diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 7be2372..894a44c --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,3 @@ venv.bak/ # mypy .mypy_cache/ - -# keys folder -keys/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100755 index 7005c2c..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,32 +0,0 @@ -# https://hub.docker.com/r/library/python/tags/ -image: python:latest - -# Change pip's cache directory to be inside the project directory since we can -# only cache local items. -variables: - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - -# Pip's cache doesn't store the python packages -# https://pip.pypa.io/en/stable/reference/pip_install/#caching -# -# If you want to also cache the installed packages, you have to install -# them in a virtualenv and cache it as well. -cache: - paths: - - .cache/pip - - venv/ - -before_script: - - python -V # Print out python version for debugging - - apt update && apt upgrade -y - - apt install -y libchromaprint-tools ffmpeg --install-recommends - - pip install -r YouTubeMDBot/requirements.txt - -test:pylint: - script: - - pip install pylint - - pylint -j 0 --exit-zero --ignored-classes=_socketobject *.py YouTubeMDBot - -test: - script: - - python -m unittest $(pwd)/YouTubeMDBot/tests/*.py diff --git a/Design/Database/YouTubeMDBot.drawio b/Design/Database/YouTubeMDBot.drawio deleted file mode 100644 index 6e7c827..0000000 --- a/Design/Database/YouTubeMDBot.drawio +++ /dev/null @@ -1 +0,0 @@ -7V1Nl5s2FP01XjaHb5vlfGSaTdq0c3qarObIINt0AE1AZMb59RVGwraEOwyWkZgqmxjBCNC9enq670nM3Jvs5dcCPG0+oximM8eKX2bu7cxxbNddkP/qkm1TsrCcpmBdJDG9aF9wn/yEtNCipVUSw/LoQoxQipOn48II5TmM8FEZKAr0fHzZCqXHd30CaygU3EcgFUv/TmK8oW/hBPvyTzBZb9id7SBszixB9LguUJXT+80cd7X715zOAKuLvmi5ATF6PihyP87cmwIh3PzKXm5gWrcta7bm7+5OnG2fu4A57vMHle/e383/zLIAeD+j4J8s+eP3X2yXPt0PkFa0Rf4qYUEfGW9ZM+1eFNZV2TP3+nmTYHj/BKL67DMhBinb4Cylp9tX3R3gAj3CG5SiYleVa1muGwTkzCpJ04PyW++jfefX5SjHXdeDNFnnpCwib0we0b1ep6Cs4bfI7whlSUR/1xXc02evHyEFS5het3ixunOUw/YsKmJYcGd29TSMtT1yTJsKFhi+nETBbrElfQaiDOJiSy6hf+Ay3tPuYof0+HlPPptRZnNAvICWAcr3dVv1HnPyg8L+JgrYAgVmTpCS+15X5Me6/kFqoEXkHm2pwBJyO9Jv4esMOabTEV9OgfrfIA5j2SG4vVkngQU8CXpyYHE5DjgdHODBzeOr2uDuG/0A0L6t2dQKY8Eoc01H7oyqIoJ9bBcGxRri1ykuwnHQ4H5He7OyAqYAJz+OH7kLBHqHLyghL3MKbaErN29K/+jQgnP1BFw9NldP0w5CPTtGtC99DklcgSQ5yKCxAkOtQKCdGfAEhDFYG4BlDfbqAfYFgFOQG4SHIuwFvm4IBwLCq6Qo8QOIIkjwMEgPQzpwtOvL8/fts7ka+WyuI8lnm4/tsy1GIgl8SfDXut9/8OnRN2oF6t+3L4cHW3qgiFieIZYEYoXv2/r4hiTnk8QT1cV3RZLAkEQCSU7rj0umNH5KSozqxmhFyOVJEXK4VP1/8GP10589UXuMATay0mCE/ROGQd1UxROVQyHEQJoSPpg4wyU7uw5MEBVG/T0CNkK97hE0tkwTj8DnR/KBHoHPexYX9whEZWMDROmq2KBsWZU6WIF4Hi4tS7QCqxUMoqiDthfp7QtubF+I3X0xbm8fTX3IyfN+pdjtDnbqQ6NF1Id7AWJ3dAkFgrG2x7zB6sZRiZXwePgHWgnPHdtKTFGBeMNQMjckOZ8krDkOSEKgv2J47ZlCzC7mpoZHRp6lBB3Yd1okmPLaiCcRSK/oiSyJ4/SUs7ofmyw5Y9PbWH3WgNNKBUw6sMQBx+vgKO9PyBtwfFFLIC129ZuBW4Z/4WoHtygdTGAIcHsPATqlLU13CDgtPrQi4pcCriDBMoKl0RjPNhS+dQy564uGYmSR0Rd1BzOl7IEkn/WgfErpixlM+pv8/iElX2lw2vO4jusNtPleyLkK/thGX0yD0p8mzEj1oInS5Bg+F8od7BpwEwh3dN9AVBrN9FDWfMHTb74g6pEGbllw875C25nVwS0qhKCKE/SwQkUGsDggmLBiv1xYvmOrjyuymYMA9feKtADeGqwHYj3n53LqE58DUeQriS/3UKJ8/ZAm+aMBWxLYngZgi+oNKB9rC/5A7gBigIGBW5Id9y1POdxTzA9hfobu8zZO0Q0kLUQNxl6IGkxRA2KNZkgyEkmmqAAx18aQZCSSTHENXdA7gGhIIoUkXVlsXADxc+uLmujhuVklnvPheFV1wJatq4sfBqKUZDbJuTAPTmQhq5ucsGjF4fYYCU7NUhVJEM81gFjUlkCBk9KIxbJ2ufE6UgjGBtkRQU6XVWYwHoix6+iHsagbRuhHx86GBuO+W93oNx6LYiGZskBQNgsJDdDDgPb1G5VFwa+AESriJF8bqOXZ7flcPdSibBdXBcAJyg3MklwwHWAWhbeoKjHKTFRP8qQq0MB8iwLaFlW4WppxWuLegzoAPcUlmfPe63YbIVgTOT2UtN/PYuz9fhj9pkWS3tFbQxIpJOnaFEp7kgSGJKOSRNTxJkAS35BkVJJ0Lf/UniS9VwIZkkghyRTTDtm0ypBkJJJMMu2wP0lClSQRZIuhS0/5TUn8cGyaTDHxkMW9dbclLrdCIRhqTFwuyiFslndxmkwy9bC3NWli7Mpowuce8guLe9OEj3ry4tnFaTLWFnqKaKI0Q9WdSxp0PD5ZbfRB53R+YpuHepfUqWomP1V6fmrYN93lcvmpjG8mP3XExZLHJFAfVA07khfNImgJUPPmXQesHQHrsm4dA7Esq25b6rMYQ1G8NNuW9QAz4MFcBCKW425cFk5RYmRu5euOfKjTUjPbGTrf4z/Jao893wvHEhn3W6bvd0n/RusYa8v0sPdKRtWqk8tZk/kwegkJl/yE8+L0mqI4ySY3r9NkoVTDtrldD21r6JJXx+NrGnvRazhFeZI5xdoThZ9PDNWdbG4SuhhbdwqnKE+yebL2NHk/Xo0oT5qNF2VtvOhagiDZMdsZdevFdqtPA/hFAOe8SNUbqzpWV7omv8d+CrbpbiW1CUGcu3Ebv6BHeQDCsUQ90gQgxmVBwL6kqkyvdKwJJlu2xutVd5CS3LiDZ7qDjtWlhXLDxT0G9b4bSWS+yCLBVHA7faqPWDuWKHTiJINicMMMB8Mw1iB+5VgTlBtbZvYYD5R+reUdjQcnPvhrLSHMdw31vYIlJn1bMPsm8nlsBLi01oXqDzY51gQVwtZB6WECdPpM65RNgFEILycYBVxgSr0+yIg7LavQd6cAOqU0VuFcq2BPcBl4f5oozmrgP9MZ8jHmvjTxeaeDnzWeoAmBDWwPLnuqLyhPPzD/VcEFtU971jU1SubgJDWt3hzUK8TZhjbONVV9vzj5Zg7ySsp5HCSHBUL48HLibmw+oxjWV/wL \ No newline at end of file diff --git a/Design/Database/YouTubeMDBot.pdf b/Design/Database/YouTubeMDBot.pdf deleted file mode 100644 index 3754129..0000000 Binary files a/Design/Database/YouTubeMDBot.pdf and /dev/null differ diff --git a/Design/Database/YouTubeMDBot.png b/Design/Database/YouTubeMDBot.png deleted file mode 100644 index c003094..0000000 Binary files a/Design/Database/YouTubeMDBot.png and /dev/null differ diff --git a/Design/Database/YouTubeMDBot.svg b/Design/Database/YouTubeMDBot.svg deleted file mode 100644 index 95d49cb..0000000 --- a/Design/Database/YouTubeMDBot.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -
User
User
id
[Not supported by viewer]
name
name
tag
tag
lang
lang
first_access
first_access
History
<b>History</b>
date
date
file_id
[Not supported by viewer]
has
has
1:1
1:1
0:N
0:N
Preferences
<b>Preferences</b>
has
has
1:1
1:1
1:1
1:1
audio_format
audio_format
audio_quality
audio_quality
send_song_link
send_song_link
ask_for_metadata
ask_for_metadata
Metadata
<b>Metadata</b>
id
[Not supported by viewer]
title
title
artist
artist
album
album
cover
cover
release_id
release_id
recording_id
recording_id
duration
duration
custom_metadata
custom_metadata
youtube_id
youtube_id
File
[Not supported by viewer]
id
[Not supported by viewer]
audio_quality
audio_quality
size
size
has
has
1:1
1:1
1:1
1:1
Playlist
<b>Playlist</b>
id
[Not supported by viewer]
Statistics
<b>Statistics</b>
times
times
has been requested
has been requested
1:1
1:1
\ No newline at end of file diff --git a/Design/Database/database_model.mwb b/Design/Database/database_model.mwb new file mode 100644 index 0000000..03ffdc9 Binary files /dev/null and b/Design/Database/database_model.mwb differ diff --git a/Design/Database/database_model.mwb.bak b/Design/Database/database_model.mwb.bak new file mode 100644 index 0000000..ea968e3 Binary files /dev/null and b/Design/Database/database_model.mwb.bak differ diff --git a/Design/Database/generated_sql_file.sql b/Design/Database/generated_sql_file.sql new file mode 100644 index 0000000..e65336e --- /dev/null +++ b/Design/Database/generated_sql_file.sql @@ -0,0 +1,251 @@ +-- MySQL Script generated by MySQL Workbench +-- lun 22 jul 2019 14:28:48 CEST +-- Model: New Model Version: 1.0 +-- MySQL Workbench Forward Engineering + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + +-- ----------------------------------------------------- +-- Schema youtubemd +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema youtubemd +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `youtubemd` DEFAULT CHARACTER SET utf8mb4 ; +SHOW WARNINGS; +USE `youtubemd` ; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`User` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`User` ( + `id` INT(64) NOT NULL DEFAULT 0, + `name` VARCHAR(45) NULL DEFAULT 'User', + `surname` VARCHAR(45) NULL, + `username` VARCHAR(45) NULL, + `lastSeen` DATETIME NOT NULL, + `firstUsage` DATETIME NOT NULL, + PRIMARY KEY (`id`)) +ENGINE = InnoDB +CHECKSUM = 1 +PACK_KEYS = 1; + +SHOW WARNINGS; +CREATE UNIQUE INDEX `id_UNIQUE` ON `youtubemd`.`User` (`id` ASC) VISIBLE; + +SHOW WARNINGS; +CREATE UNIQUE INDEX `username_UNIQUE` ON `youtubemd`.`User` (`username` ASC) VISIBLE; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`Preferences` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`Preferences` ( + `language` VARCHAR(3) NOT NULL DEFAULT 'en', + `audioQuality` ENUM('320k', '256k', '128k') NOT NULL DEFAULT '128k', + `audioSampling` ENUM('44000', '48000') NOT NULL DEFAULT '44000', + `sendSongLinks` TINYINT NOT NULL DEFAULT 0, + `User_id` INT(64) NOT NULL, + PRIMARY KEY (`User_id`), + CONSTRAINT `fk_Preferences_User` + FOREIGN KEY (`User_id`) + REFERENCES `youtubemd`.`User` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`Metadata` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`Metadata` ( + `idMetadata` INT NOT NULL AUTO_INCREMENT, + `title` VARCHAR(100) NOT NULL, + `artist` VARCHAR(60) NOT NULL, + `cover` BLOB NOT NULL, + `duration` INT NULL, + `customMetadata` TINYINT NOT NULL DEFAULT 0, + PRIMARY KEY (`idMetadata`)) +ENGINE = InnoDB +AUTO_INCREMENT = 0 +CHECKSUM = 1; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`VideoInformation` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`VideoInformation` ( + `id` VARCHAR(11) NOT NULL, + `title` VARCHAR(100) NOT NULL, + `channel` VARCHAR(60) NOT NULL, + PRIMARY KEY (`id`)) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; +CREATE UNIQUE INDEX `id_UNIQUE` ON `youtubemd`.`VideoInformation` (`id` ASC) VISIBLE; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`DownloadInformation` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`DownloadInformation` ( + `file_id` VARCHAR(50) NOT NULL, + `audioQuality` ENUM('320k', '256k', '128k') NOT NULL, + `audioSampling` ENUM('44000', '48000') NULL, + `Metadata_idMetadata` INT NOT NULL, + `VideoInformation_id` VARCHAR(11) NOT NULL, + PRIMARY KEY (`file_id`, `Metadata_idMetadata`, `VideoInformation_id`), + CONSTRAINT `fk_DownloadInformation_Metadata1` + FOREIGN KEY (`Metadata_idMetadata`) + REFERENCES `youtubemd`.`Metadata` (`idMetadata`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_DownloadInformation_VideoInformation1` + FOREIGN KEY (`VideoInformation_id`) + REFERENCES `youtubemd`.`VideoInformation` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; +CREATE UNIQUE INDEX `file_id_UNIQUE` ON `youtubemd`.`DownloadInformation` (`file_id` ASC) VISIBLE; + +SHOW WARNINGS; +CREATE INDEX `fk_DownloadInformation_Metadata1_idx` ON `youtubemd`.`DownloadInformation` (`Metadata_idMetadata` ASC) VISIBLE; + +SHOW WARNINGS; +CREATE INDEX `fk_DownloadInformation_VideoInformation1_idx` ON `youtubemd`.`DownloadInformation` (`VideoInformation_id` ASC) VISIBLE; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`History` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`History` ( + `User_id` INT(64) NOT NULL, + `DownloadInformation_file_id` VARCHAR(50) NOT NULL, + PRIMARY KEY (`User_id`, `DownloadInformation_file_id`), + CONSTRAINT `fk_History_User1` + FOREIGN KEY (`User_id`) + REFERENCES `youtubemd`.`User` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_History_DownloadInformation1` + FOREIGN KEY (`DownloadInformation_file_id`) + REFERENCES `youtubemd`.`DownloadInformation` (`file_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; +CREATE INDEX `fk_History_DownloadInformation1_idx` ON `youtubemd`.`History` (`DownloadInformation_file_id` ASC) VISIBLE; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`VideoStatistics` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`VideoStatistics` ( + `timesRequested` INT NOT NULL DEFAULT 0, + `VideoInformation_id` VARCHAR(11) NOT NULL, + PRIMARY KEY (`VideoInformation_id`), + CONSTRAINT `fk_VideoStatistics_VideoInformation1` + FOREIGN KEY (`VideoInformation_id`) + REFERENCES `youtubemd`.`VideoInformation` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`DownloadStatistics` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`DownloadStatistics` ( + `timesRequested` INT NOT NULL DEFAULT 0, + `DownloadInformation_file_id` VARCHAR(50) NOT NULL, + PRIMARY KEY (`DownloadInformation_file_id`), + CONSTRAINT `fk_DownloadStatistics_DownloadInformation1` + FOREIGN KEY (`DownloadInformation_file_id`) + REFERENCES `youtubemd`.`DownloadInformation` (`file_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`Playlist` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`Playlist` ( + `id` VARCHAR(60) NOT NULL, + PRIMARY KEY (`id`)) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; +CREATE UNIQUE INDEX `id_UNIQUE` ON `youtubemd`.`Playlist` (`id` ASC) VISIBLE; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`Playlist_has_VideoInformation` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`Playlist_has_VideoInformation` ( + `Playlist_id` VARCHAR(60) NOT NULL, + `VideoInformation_id` VARCHAR(11) NOT NULL, + PRIMARY KEY (`Playlist_id`, `VideoInformation_id`), + CONSTRAINT `fk_Playlist_has_VideoInformation_Playlist1` + FOREIGN KEY (`Playlist_id`) + REFERENCES `youtubemd`.`Playlist` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Playlist_has_VideoInformation_VideoInformation1` + FOREIGN KEY (`VideoInformation_id`) + REFERENCES `youtubemd`.`VideoInformation` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; +CREATE INDEX `fk_Playlist_has_VideoInformation_VideoInformation1_idx` ON `youtubemd`.`Playlist_has_VideoInformation` (`VideoInformation_id` ASC) VISIBLE; + +SHOW WARNINGS; +CREATE INDEX `fk_Playlist_has_VideoInformation_Playlist1_idx` ON `youtubemd`.`Playlist_has_VideoInformation` (`Playlist_id` ASC) VISIBLE; + +SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table `youtubemd`.`PlaylistStatistics` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `youtubemd`.`PlaylistStatistics` ( + `timesRequested` INT NOT NULL, + `Playlist_id` VARCHAR(60) NOT NULL, + PRIMARY KEY (`Playlist_id`), + CONSTRAINT `fk_PlaylistStatistics_Playlist1` + FOREIGN KEY (`Playlist_id`) + REFERENCES `youtubemd`.`Playlist` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +CHECKSUM = 1; + +SHOW WARNINGS; + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/Design/Database/img_database_model.png b/Design/Database/img_database_model.png new file mode 100644 index 0000000..6046766 Binary files /dev/null and b/Design/Database/img_database_model.png differ diff --git a/Design/Database/new_database_model.mwb b/Design/Database/new_database_model.mwb deleted file mode 100644 index 8a3992e..0000000 Binary files a/Design/Database/new_database_model.mwb and /dev/null differ diff --git a/Design/Database/pdf_database_model.pdf b/Design/Database/pdf_database_model.pdf new file mode 100644 index 0000000..4cdb445 Binary files /dev/null and b/Design/Database/pdf_database_model.pdf differ diff --git a/Design/Database/psql_model.sql b/Design/Database/psql_model.sql deleted file mode 100644 index 7fffb89..0000000 --- a/Design/Database/psql_model.sql +++ /dev/null @@ -1,266 +0,0 @@ --- PostgreSQL model for YouTubeMDBot application --- Created by Javinator9889 - thu, 24 October, 2019 --- Last modification: mon, 4 November, 2019 --- Version 1.1 - --- DROP schema - only for testing --- DROP SCHEMA IF EXISTS youtubemd CASCADE; --- DROP TYPE IF EXISTS AFORMAT; --- DROP TYPE IF EXISTS aquality; --- DROP TYPE IF EXISTS behaviour; - --- Custom "enum" types -CREATE TYPE AFORMAT AS ENUM ('mp3', 'm4a', 'ogg'); -CREATE TYPE AQUALITY AS ENUM ('128k', '96k'); -CREATE TYPE BEHAVIOUR AS ENUM ('always', 'not_found', 'ask', 'never'); - --- Create DB schema -CREATE SCHEMA IF NOT EXISTS youtubemd; - --- --------------------------------------- --- Table User -- --- --------------------------------------- -CREATE TABLE IF NOT EXISTS youtubemd.User -( - "id" INT PRIMARY KEY NOT NULL, - "name" VARCHAR(45), - "tag" VARCHAR(45), - "lang" VARCHAR(2), - "first_access" date -); - --- --------------------------------------------- --- Table Preferences -- --- --------------------------------------------- -CREATE TABLE IF NOT EXISTS youtubemd.Preferences -( - "audio_format" AFORMAT NOT NULL DEFAULT 'm4a', - "audio_quality" AQUALITY NOT NULL DEFAULT '128k', - "song_behaviour" BEHAVIOUR NOT NULL DEFAULT 'not_found', - "send_song_link" BOOLEAN DEFAULT False, - "user_id" INT NOT NULL, - PRIMARY KEY ("user_id"), - CONSTRAINT "fk_user_id" - FOREIGN KEY ("user_id") - REFERENCES youtubemd.User ("id") - ON DELETE CASCADE - ON UPDATE CASCADE -); - --- ------------------------------------------ --- Table YouTube -- --- ------------------------------------------ -CREATE TABLE IF NOT EXISTS youtubemd.YouTube -( - "id" VARCHAR(11) UNIQUE NOT NULL, - "times_requested" INT NOT NULL DEFAULT 0, - PRIMARY KEY ("id") -); - --- ------------------------------------------ --- Table Metadata -- --- ------------------------------------------ -CREATE TABLE IF NOT EXISTS youtubemd.Metadata -( - "id" BIGSERIAL NOT NULL, - "artist" VARCHAR(45) NOT NULL, - "album" VARCHAR(45) NOT NULL, - "cover" BYTEA NOT NULL, - "release_id" VARCHAR(36), - "recording_id" VARCHAR(36), - "duration" INT, - "title" VARCHAR(45), - "custom_metadata" BOOLEAN, - PRIMARY KEY ("id") -); - --- ---------------------------------------------------- --- Relation between YouTube and Metadata -- --- ---------------------------------------------------- -CREATE TABLE IF NOT EXISTS youtubemd.Video_Has_Metadata -( - "id" VARCHAR(11) NOT NULL, - "metadata_id" INT NOT NULL, - PRIMARY KEY ("id", "metadata_id"), - CONSTRAINT "fk_video_id" - FOREIGN KEY ("id") - REFERENCES youtubemd.YouTube ("id"), - CONSTRAINT "fk_metadata_id" - FOREIGN KEY ("metadata_id") - REFERENCES youtubemd.Metadata ("id") -); - --- -------------------------------------- --- Table File -- --- -------------------------------------- -CREATE TABLE IF NOT EXISTS youtubemd.File -( - "id" VARCHAR(50) UNIQUE NOT NULL, - "metadata_id" INT UNIQUE NOT NULL, - "audio_quality" AQUALITY NOT NULL, - "size" INT, - PRIMARY KEY ("id", "metadata_id"), - CONSTRAINT "fk_metadata_id" - FOREIGN KEY ("metadata_id") - REFERENCES youtubemd.Metadata ("id") -); - --- ----------------------------------------- --- Table History -- --- ----------------------------------------- -CREATE TABLE IF NOT EXISTS youtubemd.History -( - "id" BIGSERIAL NOT NULL, - "file_id" VARCHAR(50) NOT NULL, - "user_id" INT NOT NULL, - "metadata_id" INT NOT NULL, - "date" date, - PRIMARY KEY ("id", "file_id", "user_id", "metadata_id"), - CONSTRAINT "fk_user_id" - FOREIGN KEY ("user_id") - REFERENCES youtubemd.User ("id") - ON DELETE CASCADE - ON UPDATE CASCADE, - CONSTRAINT "fk_file_id" - FOREIGN KEY ("file_id") - REFERENCES youtubemd.File ("id"), - CONSTRAINT "fk_metadata_id" - FOREIGN KEY ("metadata_id") - REFERENCES youtubemd.Metadata ("id") -); - --- ------------------------------------------ --- Table Playlist -- --- ------------------------------------------ -CREATE TABLE IF NOT EXISTS youtubemd.Playlist -( - "id" VARCHAR(22) NOT NULL UNIQUE, - PRIMARY KEY ("id") -); - --- ---------------------------------------------- --- Table YouTube stats -- --- ---------------------------------------------- -CREATE TABLE IF NOT EXISTS youtubemd.YouTubeStats -( - "id" VARCHAR(11) NOT NULL UNIQUE, - "daily_requests" INT NOT NULL DEFAULT 1, - "weekly_requests" INT NOT NULL DEFAULT 1, - "monthly_requests" INT NOT NULL DEFAULT 1, - PRIMARY KEY ("id"), - CONSTRAINT "fk_youtube_id" - FOREIGN KEY ("id") - REFERENCES youtubemd.YouTube ("id") -); - --- Additional indexes -CREATE INDEX youtubemd.user_preferences_ix ON youtubemd.Preferences ("user_id"); -CREATE INDEX youtubemd.video_metadata_ix ON youtubemd.Video_Has_Metadata ("id", "metadata_id"); -CREATE INDEX youtubemd.history_ix ON youtubemd.History ("id", "file_id", "user_id", "metadata_id"); - --- Trigger that updated the different stats -CREATE FUNCTION youtubemd.process_stats() RETURNS trigger AS -$$ -DECLARE - daily_value INT; - weekly_value INT; - monthly_value INT; -BEGIN - IF (SELECT EXISTS(SELECT 1 FROM youtubemd.YouTubeStats WHERE youtubemd.YouTubeStats.id = NEW.id)) THEN - SELECT INTO daily_value, weekly_value, monthly_value youtubemd.YouTubeStats.daily_requests, - youtubemd.YouTubeStats.weekly_requests, - youtubemd.YouTubeStats.monthly_requests - FROM youtubemd.YouTubeStats - WHERE youtubemd.YouTubeStats.id = NEW.id; - daily_value = daily_value + 1; - weekly_value = weekly_value + 1; - monthly_value = monthly_value + 1; - - UPDATE youtubemd.YouTubeStats - SET daily_requests = daily_value, - weekly_requests = weekly_value, - monthly_requests = monthly_value - WHERE id = NEW.id; - ELSE - INSERT INTO youtubemd.YouTubeStats (id) VALUES (NEW.id); - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Complementary functions with useful operations -CREATE FUNCTION youtubemd.top_10_daily() - RETURNS TABLE - ( - id VARCHAR(11), - daily_requests INT - ) -AS -$$ -BEGIN - RETURN QUERY SELECT DISTINCT youtubemd.YouTubeStats.id, youtubemd.YouTubeStats.daily_requests - FROM youtubemd.youtubestats - ORDER BY daily_requests DESC - FETCH FIRST 10 ROWS ONLY; -END; -$$ LANGUAGE plpgsql; - -CREATE FUNCTION youtubemd.top_10_weekly() - RETURNS TABLE - ( - id VARCHAR(11), - weekly_requests INT - ) -AS -$$ -BEGIN - RETURN QUERY SELECT DISTINCT youtubemd.YouTubeStats.id, youtubemd.YouTubeStats.weekly_requests - FROM youtubemd.youtubestats - ORDER BY weekly_requests DESC - FETCH FIRST 10 ROWS ONLY; -END; -$$ LANGUAGE plpgsql; - -CREATE FUNCTION youtubemd.top_10_monthly() - RETURNS TABLE - ( - id VARCHAR(11), - monthly_requests INT - ) -AS -$$ -BEGIN - RETURN QUERY SELECT DISTINCT youtubemd.YouTubeStats.id, youtubemd.YouTubeStats.monthly_requests - FROM youtubemd.youtubestats - ORDER BY monthly_requests DESC - FETCH FIRST 10 ROWS ONLY; -END; -$$ LANGUAGE plpgsql; - -CREATE FUNCTION youtubemd.clear_daily_stats() AS -$$ -BEGIN - UPDATE youtubemd.YouTubeStats SET daily_requests = 0; -END; -$$ LANGUAGE plpgsql; - -CREATE FUNCTION youtubemd.clear_weekly_stats() AS -$$ -BEGIN - UPDATE youtubemd.YouTubeStats SET weekly_requests = 0; -END; -$$ LANGUAGE plpgsql; - -CREATE FUNCTION youtubemd.clear_monthly_stats() AS -$$ -BEGIN - UPDATE youtubemd.YouTubeStats SET monthly_requests = 0; -END; -$$ LANGUAGE plpgsql; - --- Init the trigger -CREATE TRIGGER stats_update - AFTER INSERT OR UPDATE - ON youtubemd.YouTube - FOR EACH ROW -EXECUTE PROCEDURE youtubemd.process_stats(); diff --git a/Design/Database/vect_database_model.svg b/Design/Database/vect_database_model.svg new file mode 100644 index 0000000..fe40b7a --- /dev/null +++ b/Design/Database/vect_database_model.svg @@ -0,0 +1,3111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Design/SRS/Software Requirement Specification.docx b/Design/SRS/Software Requirement Specification.docx old mode 100755 new mode 100644 diff --git a/Design/SRS/Software Requirement Specification.pdf b/Design/SRS/Software Requirement Specification.pdf old mode 100755 new mode 100644 diff --git a/Design/logo/logo.png b/Design/logo/logo.png old mode 100755 new mode 100644 diff --git a/Design/logo/logo.psd b/Design/logo/logo.psd old mode 100755 new mode 100644 diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/YouTubeMDBot/.idea/.gitignore b/YouTubeMDBot/.idea/.gitignore deleted file mode 100755 index 0e40fe8..0000000 --- a/YouTubeMDBot/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -# Default ignored files -/workspace.xml \ No newline at end of file diff --git a/YouTubeMDBot/.idea/YouTubeMDBot.iml b/YouTubeMDBot/.idea/YouTubeMDBot.iml deleted file mode 100755 index 6711606..0000000 --- a/YouTubeMDBot/.idea/YouTubeMDBot.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/codeStyles/codeStyleConfig.xml b/YouTubeMDBot/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100755 index a55e7a1..0000000 --- a/YouTubeMDBot/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/dictionaries/javinator9889.xml b/YouTubeMDBot/.idea/dictionaries/javinator9889.xml deleted file mode 100755 index 787b6ce..0000000 --- a/YouTubeMDBot/.idea/dictionaries/javinator9889.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - acoustid - ffmpeg - fpcalc - javinator - - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/git_toolbox_prj.xml b/YouTubeMDBot/.idea/git_toolbox_prj.xml deleted file mode 100644 index c7846c0..0000000 --- a/YouTubeMDBot/.idea/git_toolbox_prj.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/inspectionProfiles/profiles_settings.xml b/YouTubeMDBot/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100755 index 105ce2d..0000000 --- a/YouTubeMDBot/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/misc.xml b/YouTubeMDBot/.idea/misc.xml deleted file mode 100755 index 8656114..0000000 --- a/YouTubeMDBot/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/modules.xml b/YouTubeMDBot/.idea/modules.xml deleted file mode 100755 index 48cc268..0000000 --- a/YouTubeMDBot/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/vcs.xml b/YouTubeMDBot/.idea/vcs.xml deleted file mode 100755 index 6c0b863..0000000 --- a/YouTubeMDBot/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/YouTubeMDBot/__init__.py b/YouTubeMDBot/__init__.py deleted file mode 100755 index 986bc7c..0000000 --- a/YouTubeMDBot/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from .api import YouTubeAPI -from .api import YouTubeVideoData - -from .audio import FPCalc -from .audio import FFmpegOGG -from .audio import FFmpegMP3 -from .audio import FFmpegOpener -from .audio import ffmpeg_available - -from .commands import StartHandler - -from .constants import * - -from .decorators import restricted -from .decorators import send_action - -from .downloader import YouTubeDownloader - -from .errors import EmptyBodyError - -from .logging_utils import LoggingHandler -from .logging_utils import setup_logging - -from .metadata import AudioMetadata -from .metadata import MetadataIdentifier -from .metadata import YouTubeMetadataIdentifier - -from .utils import get_yt_video_id diff --git a/YouTubeMDBot/__main__.py b/YouTubeMDBot/__main__.py deleted file mode 100755 index 8a858f1..0000000 --- a/YouTubeMDBot/__main__.py +++ /dev/null @@ -1,15 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . diff --git a/YouTubeMDBot/api/__init__.py b/YouTubeMDBot/api/__init__.py deleted file mode 100755 index 4ba8496..0000000 --- a/YouTubeMDBot/api/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..api.youtube_api import YouTubeAPI -from ..api.youtube_api import YouTubeVideoData diff --git a/YouTubeMDBot/api/youtube_api.py b/YouTubeMDBot/api/youtube_api.py deleted file mode 100755 index d8ff82f..0000000 --- a/YouTubeMDBot/api/youtube_api.py +++ /dev/null @@ -1,143 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from isodate import parse_duration - -from ..constants import YOUTUBE -from ..errors import EmptyBodyError - - -class YouTubeVideoData: - """ - Obtains YouTube video data and wraps it inside this class. All fields are direct - access available, so it is possible to access them directly: - - title - - id - - thumbnail - - artist - - duration - - views - - likes - - dislikes - """ - - def __init__(self, data: dict, ignore_errors: bool = False): - """ - By passing a dict with the YouTube data (YouTube API v3), generate and obtain - the information available from the result. - :param data: a dictionary with the information obtained from YouTube API. - :param ignore_errors: whether to ignore or not errors (do not raise exceptions). - :raises EmptyBodyError when there is no information available and ignored - errors is False. - """ - if not data.get("items"): - raise EmptyBodyError("The data object has no items") - self.id: str = "" - self.title: str = "" - self.thumbnail: str = "" - self.artist: str = "" - self.duration: float = 0.0 - self.views: int = 0 - self.likes: int = 0 - self.dislikes: int = 0 - if len(data.get("items")) >= 1: - content = data.get("items")[0] - snippet = content.get("snippet") - details = content.get("contentDetails") - statistics = content.get("statistics") - if not snippet and not ignore_errors: - raise EmptyBodyError("No information available to requested video") - elif not snippet and ignore_errors: - snippet_available = False - else: - snippet_available = True - if not details and not ignore_errors: - raise EmptyBodyError("No video details available") - elif not details and ignore_errors: - details_available = False - else: - details_available = True - if not statistics and not ignore_errors: - raise EmptyBodyError("No statistics available") - elif not statistics and ignore_errors: - statistics_available = False - else: - statistics_available = True - c_id = content.get("id", "") - self.id = c_id.get("videoId", "") if isinstance(c_id, dict) else c_id - if snippet_available: - self.title = snippet["title"] - try: - self.thumbnail = snippet["thumbnails"]["maxres"]["url"] - except KeyError: - try: - self.thumbnail = snippet["thumbnails"]["high"]["url"] - except KeyError: - try: - self.thumbnail = snippet["thumbnails"]["medium"]["url"] - except KeyError: - self.thumbnail = snippet["thumbnails"]["default"]["url"] - self.artist = snippet["channelTitle"] - if details_available: - self.duration = parse_duration(details["duration"]).total_seconds() - if statistics_available: - self.views = int(statistics["viewCount"]) - self.likes = int(statistics["likeCount"]) - self.dislikes = int(statistics["dislikeCount"]) - - -class YouTubeAPI: - """ - Wrapper for the YouTube API data. Allows the developer searching for videos and, - with a given video ID, obtain its data. - """ - - def __init__(self): - from googleapiclient.discovery import build - - self.__youtube = build(serviceName=YOUTUBE["api"]["name"], - version=YOUTUBE["api"]["version"], - developerKey=YOUTUBE["key"]) - - def search(self, term: str) -> dict: - """ - Searchs for a video with the specified term. - :param term: the search term. - :return: dict with YouTube data - can be wrapped inside "YouTubeVideoData" class. - """ - return self.__youtube.search().list( - q=term, - type="video", - part="id,snippet", - maxResults=1 - ).execute() - - @staticmethod - def video_details(video_id: str) -> YouTubeVideoData: - """ - Generates a "YouTubeVideoData" object wrapper with the video ID information. - :param video_id: YouTube video ID. - :return: YouTubeVideoData object with the available metadata. - """ - try: - import ujson as json - except ImportError: - import json - from urllib.request import urlopen - - youtube_information = YOUTUBE.copy() - api_url = youtube_information["endpoint"].format(video_id, YOUTUBE["key"]) - data = urlopen(url=api_url) - return YouTubeVideoData(data=json.loads(data.read()), ignore_errors=True) diff --git a/YouTubeMDBot/audio/__init__.py b/YouTubeMDBot/audio/__init__.py deleted file mode 100755 index 49eab31..0000000 --- a/YouTubeMDBot/audio/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..audio.ffmpeg import FFmpegOpener -from ..audio.ffmpeg import FFmpegOGG -from ..audio.ffmpeg import FFmpegMP3 -from ..audio.ffmpeg import ffmpeg_available -from ..audio.fpcalc import FPCalc diff --git a/YouTubeMDBot/audio/ffmpeg.py b/YouTubeMDBot/audio/ffmpeg.py deleted file mode 100755 index f94c5ae..0000000 --- a/YouTubeMDBot/audio/ffmpeg.py +++ /dev/null @@ -1,171 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from abc import ABC -from abc import abstractmethod -from typing import List -from subprocess import PIPE -from subprocess import Popen - -from ..constants import FFMPEG_OPENER -from ..constants import FFMPEG_CONVERTER - - -def ffmpeg_available() -> bool: - """ - Checks if "ffmpeg" is installed or not. - :return: True if installed, else False - """ - try: - proc = Popen(["ffmpeg", "-version"], - stdout=PIPE, - stderr=PIPE) - except OSError: - return False - else: - proc.wait() - return proc.returncode == 0 - - -class FFmpeg(ABC): - """ - Base abstract class for the FFmpeg operators. All classes that works with FFmpeg - must inherit from this class in order to maintain readability and code optimization. - - Allows execution of the ffmpeg command by using the subprocess module. Everything - is working with PIPEs, so there is no directly discs operations (everything is - loaded and working with RAM). - """ - - def __init__(self, data: bytes, command: List[str] = None): - """ - Creates the class by passing the data which will be processed and the command ( - by default, None). - :param data: audio data that will be processed. - :param command: the ffmpeg command. - """ - self._data = data - self.__command = command - self.__out = None - self.__err = None - - def process(self) -> int: - """ - Runs the ffmpeg command in a separate process and pipes both stdout and stderr. - :return: the return code of the operation ('0' if everything is OK, > 0 if not). - """ - proc = Popen(self.__command, stdout=PIPE, stderr=PIPE, stdin=PIPE) - self.__out, self.__err = proc.communicate(self._data) - return proc.returncode - - def get_command(self) -> List[str]: - """ - Get the command for editing. - :return: List[str] with the command - as this is a pointer, all editions done - to the list are directly changing the self object. - """ - return self.__command - - def set_command(self, command: List[str]): - """ - Sets the new list, overriding every old implementation. - :param command: the new command. - """ - self.__command = command - - def get_output(self) -> bytes: - """ - Gets the stdout of the process. - :return: bytes with the command output. - """ - return self.__out - - def get_extra(self) -> bytes: - """ - Gets the stderr of the process. - :return: bytes with extra information. - """ - return self.__err - - -class FFmpegOpener(FFmpeg): - """ - Opens and produces and audio in PWM mode. - """ - - def __init__(self, data: bytes): - super().__init__(data=data, command=FFMPEG_OPENER.copy()) - - -class FFmpegExporter(FFmpeg): - """ - Base class for the exporter options available in ffmpeg. - All classes that are developed for converting audio files must inherit from this - class and implement the "convert" method. - """ - - def __init__(self, data: bytes, bitrate: str = None): - """ - Generates a new instance of the class. - :param data: the audio data. - :param bitrate: the new bitrate of the audio data, or None for keeping its - default. - """ - super().__init__(data=data, command=FFMPEG_CONVERTER.copy()) - self._bitrate = bitrate - - @abstractmethod - def convert(self) -> int: - """ - Converts the audio to the desired format. - :return: the operation result code. - :raises NotImplementedError when trying to access this method directly on super - class. - """ - raise NotImplementedError - - -class FFmpegMP3(FFmpegExporter): - """ - Exports audio data to MP3 format. - """ - def convert(self) -> int: - command = super().get_command() - if self._bitrate: - command.append("-b:a") - command.append(self._bitrate) - command.append("-acodec") - command.append("libmp3lame") - command.append("-f") - command.append("mp3") - command.append("-") - return self.process() - - -class FFmpegOGG(FFmpegExporter): - """ - Exports audio data to OGG format. - """ - def convert(self) -> int: - command = super().get_command() - if self._bitrate: - command.append("-b:a") - command.append(self._bitrate) - command.append("-c:a") - command.append("libvorbis") - command.append("-f") - command.append("ogg") - command.append("-") - return self.process() diff --git a/YouTubeMDBot/audio/fpcalc.py b/YouTubeMDBot/audio/fpcalc.py deleted file mode 100755 index 5629b12..0000000 --- a/YouTubeMDBot/audio/fpcalc.py +++ /dev/null @@ -1,70 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import re -from subprocess import PIPE -from subprocess import Popen - -from ..constants import FPCALC - - -def is_fpcalc_available() -> bool: - """ - Checks if ffmpeg is installed in the system. - :return: True if available, else False. - """ - try: - proc = Popen(["fpcalc", "-v"], stdout=PIPE, stderr=PIPE) - except OSError: - return False - else: - proc.wait() - - -class FPCalc: - """ - Calculates audio fingerprint by passing the audio bytes. - It operates with pipes so no file is created. - """ - def __init__(self, audio: bytes): - """ - Creates the FPCalc object. - :param audio: the audio bytes. - """ - fpcalc = Popen(FPCALC, stdout=PIPE, stdin=PIPE) - out, _ = fpcalc.communicate(audio) - res = out.decode("utf-8") - - duration_pattern = "[^=]\\d+\\n" - fingerprint_pattern = "[^=]*$" - duration = re.search(duration_pattern, res) - fingerprint = re.search(fingerprint_pattern, res) - - self.__duration: int = int(duration.group(0)) - self.__fp: str = str(fingerprint.group(0)) - - def duration(self) -> int: - """ - Obtains the audio duration in seconds. - :return: duration in seconds. - """ - return self.__duration - - def fingerprint(self) -> str: - """ - Obtains the audio fingerprint. - :return: fingerprint in seconds. - """ - return self.__fp diff --git a/YouTubeMDBot/bot.py b/YouTubeMDBot/bot.py deleted file mode 100755 index 10b68d9..0000000 --- a/YouTubeMDBot/bot.py +++ /dev/null @@ -1,37 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from telegram.ext import Updater -from telegram.ext import CommandHandler - -PROGRAM_ARGS = None - - -def main(args: dict): - global PROGRAM_ARGS - PROGRAM_ARGS = args - updater = Updater(args["token"], workers=args["workers"]) - - dispatcher = updater.dispatcher - dispatcher.add_handler(CommandHandler("hello", main)) - - if args["use_webhook"]: - updater.start_webhook(listen=args["ip"], - port=args["port"], - url_path=args["token"], - webhook_url=args["public_url"] + '/' + args["token"]) - else: - updater.start_polling(args["poll_interval"]) - updater.idle() diff --git a/YouTubeMDBot/commands/StartHandler.py b/YouTubeMDBot/commands/StartHandler.py deleted file mode 100755 index 8d18c8e..0000000 --- a/YouTubeMDBot/commands/StartHandler.py +++ /dev/null @@ -1,24 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -class StartHandler(object): - def __init__(self): - self._user_data = {} - - def start(self, bot, update): - pass - # TODO diff --git a/YouTubeMDBot/commands/__init__.py b/YouTubeMDBot/commands/__init__.py deleted file mode 100755 index 8a858f1..0000000 --- a/YouTubeMDBot/commands/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . diff --git a/YouTubeMDBot/constants/__init__.py b/YouTubeMDBot/constants/__init__.py deleted file mode 100755 index 12af4b1..0000000 --- a/YouTubeMDBot/constants/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..constants.app_constants import ACOUSTID_KEY -from ..constants.app_constants import FPCALC -from ..constants.app_constants import YDL_CLI_OPTIONS -from ..constants.app_constants import YOUTUBE -from ..constants.app_constants import PROGRAM_ARGS -from ..constants.app_constants import FFMPEG_OPENER -from ..constants.app_constants import FFMPEG_CONVERTER diff --git a/YouTubeMDBot/constants/app_constants.py b/YouTubeMDBot/constants/app_constants.py deleted file mode 100755 index 1ef1e70..0000000 --- a/YouTubeMDBot/constants/app_constants.py +++ /dev/null @@ -1,43 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import os -import sys - -PROGRAM_ARGS = sys.argv -# YouTube DL options -YDL_CLI_OPTIONS = ["youtube-dl", "--format", "bestaudio[ext=m4a]", "--quiet", "--output", - "-"] - -# FPCalc command -FPCALC = ["fpcalc", "-"] - -# API keys -ACOUSTID_KEY = os.environ["ACOUSTID_KEY"] -YOUTUBE = { - "key": os.environ["YOUTUBE_KEY"], - "api": { - "name": "youtube", - "version": "v3" - }, - "endpoint": - "https://www.googleapis.com/youtube/v3/videos?" - "part=id,snippet,contentDetails,statistics&id={0}&key={1}" -} - -# FFmpeg commands -FFMPEG_OPENER = "ffmpeg -i - -f s16le -".split(" ") -FFMPEG_CONVERTER = ["ffmpeg", "-i", "-", "-vn", "-map_metadata", "0", - "-movflags", "use_metadata_tags"] diff --git a/YouTubeMDBot/decorators/__init__.py b/YouTubeMDBot/decorators/__init__.py deleted file mode 100755 index 9a87cd2..0000000 --- a/YouTubeMDBot/decorators/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..decorators.decorators import restricted -from ..decorators.decorators import send_action diff --git a/YouTubeMDBot/decorators/decorators.py b/YouTubeMDBot/decorators/decorators.py deleted file mode 100755 index b3463c4..0000000 --- a/YouTubeMDBot/decorators/decorators.py +++ /dev/null @@ -1,50 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from functools import wraps - -from ..constants import PROGRAM_ARGS - - -def send_action(action): - """ - Sends an action while processing a command. - :param action: the action to be performed. - :return: itself. - """ - - def decorator(func): - @wraps(func) - def command_func(update, context, *args, **kwargs): - context.bot.send_chat_action(chat_id=update.effective_message.chat_id, action=action) - return func(update, context, *args, **kwargs) - return command_func - - return decorator - - -def restricted(func): - """ - Restricts a specific function to be accessed from non-authorized users. - :param func: function to be wrapped. - :return: itself. - """ - @wraps(func) - def wrapped(update, context, *args, **kwargs): - user_id = update.effective_user.id - if user_id not in PROGRAM_ARGS["admin"]: - return - return func(update, context, *args, **kwargs) - return wrapped diff --git a/YouTubeMDBot/downloader/__init__.py b/YouTubeMDBot/downloader/__init__.py deleted file mode 100755 index 89c3cee..0000000 --- a/YouTubeMDBot/downloader/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..downloader.youtube_downloader import YouTubeDownloader diff --git a/YouTubeMDBot/downloader/youtube_downloader.py b/YouTubeMDBot/downloader/youtube_downloader.py deleted file mode 100755 index 69c8edb..0000000 --- a/YouTubeMDBot/downloader/youtube_downloader.py +++ /dev/null @@ -1,58 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from io import BytesIO -from typing import Tuple - -from ..constants.app_constants import YDL_CLI_OPTIONS - - -class YouTubeDownloader: - """ - Download a YouTube video directly into memory. - """ - def __init__(self, url: str): - """ - Creates the YouTubeDownloader object. Call "download" for obtaining the video. - :param url: the video URL. - """ - self.__url: str = url - self.__options: list = YDL_CLI_OPTIONS.copy() - self.__options.append(self.__url) - - def download(self) -> Tuple[BytesIO, bytes]: - """ - Downloads the YouTube video directly into memory by using pipes. - :return: a tuple with "BytesIO" and "bytes". - """ - import subprocess - - proc = subprocess.Popen(self.__options, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() - retcode = proc.returncode - if retcode == 0: - return BytesIO(stdout), stdout - else: - raise RuntimeError("youtube-dl downloader exception - more info: " + - str(stderr.decode("utf-8"))) - - def get_url(self) -> str: - """ - Obtains the video URL. - :return: str with the URL. - """ - return self.__url diff --git a/YouTubeMDBot/errors/EmptyBodyError.py b/YouTubeMDBot/errors/EmptyBodyError.py deleted file mode 100755 index a2e8f81..0000000 --- a/YouTubeMDBot/errors/EmptyBodyError.py +++ /dev/null @@ -1,23 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -class EmptyBodyError(Exception): - """ - Raises an exception when the body of the json data is empty (e.g.: there is no - video information) - """ - pass diff --git a/YouTubeMDBot/errors/__init__.py b/YouTubeMDBot/errors/__init__.py deleted file mode 100755 index 98ce0a2..0000000 --- a/YouTubeMDBot/errors/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..errors.EmptyBodyError import EmptyBodyError diff --git a/YouTubeMDBot/logging_utils/__init__.py b/YouTubeMDBot/logging_utils/__init__.py deleted file mode 100755 index 45c2549..0000000 --- a/YouTubeMDBot/logging_utils/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..logging_utils.utils import LoggingHandler -from ..logging_utils.utils import setup_logging diff --git a/YouTubeMDBot/logging_utils/utils.py b/YouTubeMDBot/logging_utils/utils.py deleted file mode 100755 index 3b6ac76..0000000 --- a/YouTubeMDBot/logging_utils/utils.py +++ /dev/null @@ -1,164 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import logging - - -def cleanup_old_logs(log_file: str): - """ - Cleans-up the old log files. - :param log_file: log filename that must be cleaned. - """ - import tarfile - import os - - tar_log_filename = log_file + ".tar.gz" - - if os.path.exists(log_file): - if os.path.exists(tar_log_filename): - os.remove(tar_log_filename) - with tarfile.open(tar_log_filename, "w:gz") as tar: - tar.add(log_file, arcname=os.path.basename(log_file)) - tar.close() - os.remove(log_file) - - -def setup_logging(logger_name: str, log_file: str, level=logging.DEBUG, - formatter: str = "%(process)d - %(asctime)s | [%(levelname)s]: %(message)s"): - """ - Creates a new logging which can log to stdout or file. - :param logger_name: the logger name. - :param log_file: log filename. - :param level: logging level. - :param formatter: the logging formatter. - :return: the logging file handler. - """ - from os import path - from os import makedirs - - log_dir = path.dirname(path.abspath(log_file)) - if not path.exists(log_dir): - makedirs(log_dir) - - cleanup_old_logs(log_file) - new_logging = logging.getLogger(logger_name) - logging_formatter = logging.Formatter(formatter) - logging_file_handler = logging.FileHandler(log_file, mode="w") - - logging_file_handler.setFormatter(logging_formatter) - - new_logging.setLevel(level) - new_logging.addHandler(logging_file_handler) - - return logging_file_handler - - -class LoggingHandler: - """ - LoggingHandler singleton class that outputs to multiple logging instances. - """ - - class __LoggingHandler: - def __init__(self, logs: list): - self.__logs = logs - - def debug(self, msg): - for log in self.__logs: - log.debug(msg) - - def info(self, msg): - for log in self.__logs: - log.info(msg) - - def error(self, msg): - for log in self.__logs: - log.error(msg) - - def warning(self, msg): - for log in self.__logs: - log.warning(msg) - - def critical(self, msg): - for log in self.__logs: - log.critical(msg) - - def get_loggers(self) -> list: - return self.__logs - - __instance = None - - def __new__(cls, *args, **kwargs): - """ - Generates a new instance. - :param args: not used. - :param kwargs: "logs" is a list instance that must be provided the first time - this class is created. - :return: the LoggingHandler instance. - """ - if not LoggingHandler.__instance: - logs = kwargs.get("logs") - if not logs or len(logs) == 0: - raise AttributeError( - "At least kwarg \"log\" (a list of the loggers) must be provided") - LoggingHandler.__instance = LoggingHandler.__LoggingHandler(logs) - return LoggingHandler.__instance - - def __getattr__(self, item): - return getattr(self.__instance, item) - - def __setattr__(self, key, value): - return setattr(self.__instance, key, value) - - def debug(self, msg): - """ - Debugs to loggers - :param msg: message to debug - """ - self.__instance.debug(msg) - - def info(self, msg): - """ - Info to loggers - :param msg: message to info - """ - self.__instance.info(msg) - - def error(self, msg): - """ - Error to loggers - :param msg: message to error - """ - self.__instance.error(msg) - - def warning(self, msg): - """ - Warns to loggers - :param msg: message to warn - """ - self.__instance.warning(msg) - - def critical(self, msg): - """ - Critical to loggers - :param msg: message to critical - """ - self.__instance.critical(msg) - - def get_loggers(self) -> list: - """ - Obtains the list of loggers. - :return: the list of loggers. - """ - return self.__instance.get_loggers() diff --git a/YouTubeMDBot/metadata/AudioMetadata.py b/YouTubeMDBot/metadata/AudioMetadata.py deleted file mode 100755 index 77754e1..0000000 --- a/YouTubeMDBot/metadata/AudioMetadata.py +++ /dev/null @@ -1,78 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from mutagen.mp4 import MP4 -from mutagen.mp4 import MP4Cover -from io import BytesIO - - -class AudioMetadata: - """ - Wrapper class for setting the audio metadata to the downloaded YouTube video - object. By using this class, it is possible to set the required information by - using mutagen without having to remember the metadata keys. - """ - def __init__(self, audio: BytesIO): - """ - Generates a new instance. - :param audio: the audio metadata, in BytesIO, in MP4 format. - """ - self._audio = MP4(audio) - self._data = audio - - def set_title(self, title: str): - """ - Sets the audio title. - :param title: the audio title. - """ - self._audio[u"\xa9nam"] = title - - def set_artist(self, artist: str): - """ - Sets the audio artist. - :param artist: the audio artist. - """ - self._audio[u"\xa9ART"] = artist - - def set_album(self, album: str): - """ - Sets the audio album. - :param album: the audio album - """ - self._audio[u"\xa9alb"] = album - - def set_extras(self, extras: list): - """ - Sets the audio extras. - :param extras: a list of extras that will be added to the audio information. - """ - self._audio[u"\xa9cmt"] = '; '.join(map(str, extras)) - - def set_cover(self, cover: bytes): - """ - Sets the audio cover. - :param cover: the audio cover. - """ - mp4_cover = MP4Cover(cover, MP4Cover.FORMAT_JPEG) - self._audio[u"covr"] = [mp4_cover] - - def save(self) -> BytesIO: - """ - Saves the new metadata into the audio file object. - :return: the audio file object with the new metadata. - """ - self._data.seek(0) - self._audio.save(self._data) - return self._data diff --git a/YouTubeMDBot/metadata/MetadataIdentifier.py b/YouTubeMDBot/metadata/MetadataIdentifier.py deleted file mode 100755 index d3396aa..0000000 --- a/YouTubeMDBot/metadata/MetadataIdentifier.py +++ /dev/null @@ -1,175 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -try: - import ujson as json -except ImportError: - import json -import acoustid -import musicbrainzngs - -from ..audio import FPCalc -from ..api import YouTubeAPI -from ..utils import youtube_utils -from ..constants import ACOUSTID_KEY -from ..downloader import YouTubeDownloader - - -class MetadataIdentifier: - """ - Base identifier class. By using the audio data, calculates and generates a - fingerprint for searching across the MusicBrainz database. - - Once the audio has been identified, the available params and information are: - - audio (bytes) - - result (json) - - artist (str) - - title (str) - - release_id (str) - - recording_id (str) - - score (float) - - cover (bytes) - - album (str) - - duration (int) - - youtube_data (bool) - - youtube_id (str) - """ - def __init__(self, audio: bytes): - """ - Generates a new instance of the MetadataIdentifier class. - :param audio: the audio data, in bytes. - """ - self.audio = audio - self.result: json = None - self.artist: str = "" - self.title: str = "" - self.release_id: str = "" - self.recording_id: str = "" - self.score: float = 0.0 - self.cover: bytes = bytes(0) - self.album: str = "" - self.duration: int = 0 - self.youtube_data: bool = False - self.youtube_id: str = "" - - @staticmethod - def _is_valid_result(data: json) -> bool: - """ - Checks whether the obtained result, in json, is valid or not, by checking for - certain keys that must exist. - :param data: the result in json. - :return: 'True' if the result is valid, else 'False'. - """ - if "results" not in data: - return False - elif data["status"] != "ok": - return False - elif len(data["results"]) == 0: - return False - else: - if "recordings" not in data["results"][0]: - return False - else: - return True - - def identify_audio(self) -> bool: - """ - Tries to identify the audio by using the audio fingerprint. If the audio has - been successfully identified, then obtains all the data related to it. - :return: 'True' if the result is valid (the audio was correctly identified), - else 'False'. - """ - fingerprint = FPCalc(self.audio) - data: json = acoustid.lookup(apikey=ACOUSTID_KEY, - fingerprint=fingerprint.fingerprint(), - duration=fingerprint.duration(), - meta="recordings releaseids releasegroups") - self.result = data - is_valid = self._is_valid_result(data) - if is_valid: - for result in data["results"]: - if "recordings" not in result: - break - self.score = result["score"] - for recording in result["recordings"]: - if recording.get("artists"): - names = [artist["name"] for artist in recording["artists"]] - self.artist = "; ".join(names) - else: - self.artist = "Unknown" - self.title = recording["title"] - if recording.get("releasegroups"): - self.release_id = \ - recording["releasegroups"][0]["releases"][0]["id"] - self.album = recording["releasegroups"][0]["title"] - self.cover = musicbrainzngs.get_image_front(self.release_id) - self.recording_id = recording["id"] - self.duration = recording["duration"] - is_valid = True - break - break - return is_valid - - -class YouTubeMetadataIdentifier(MetadataIdentifier): - """ - Identifies YouTube metadata by using MusicBrainz database and YouTube metadata. If - the first identification (MusicBrainz) fails, then fallback to YouTube - identification if the "downloader" was provided. - - Once the audio has been identified, the available params and information are: - - audio (bytes) - - result (json) - - artist (str) - - title (str) - - release_id (str) - - recording_id (str) - - score (float) - - cover (bytes) - - album (str) - - duration (int) - - youtube_data (bool) - - youtube_id (str) - - If "youtube_data" is True, then only audio, title, artist, duration, cover and - youtube_id are available. - """ - def __init__(self, audio: bytes, downloader: YouTubeDownloader = None): - """ - Generates a new instance of the MetadataIdentifier class. - :param audio: the audio data, in bytes. - :param downloader: a downloader object, for obtaining the video information if - MusicBrainz fails. - """ - super().__init__(audio) - self._downloader = downloader - - def identify_audio(self) -> bool: - valid = super().identify_audio() - if not valid: - if self._downloader: - from urllib.request import urlopen - - video_id = youtube_utils.get_yt_video_id(self._downloader.get_url()) - video_data = YouTubeAPI.video_details(video_id) - self.title = video_data.title - self.artist = video_data.artist - self.duration = video_data.duration - self.cover = urlopen(video_data.thumbnail).read() - self.youtube_id = video_data.id - self.youtube_data = True - - valid = True - return valid diff --git a/YouTubeMDBot/metadata/__init__.py b/YouTubeMDBot/metadata/__init__.py deleted file mode 100755 index 4abadb9..0000000 --- a/YouTubeMDBot/metadata/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..metadata.MetadataIdentifier import MetadataIdentifier -from ..metadata.MetadataIdentifier import YouTubeMetadataIdentifier - -from ..metadata.AudioMetadata import AudioMetadata diff --git a/YouTubeMDBot/requirements.txt b/YouTubeMDBot/requirements.txt deleted file mode 100755 index e341c34..0000000 --- a/YouTubeMDBot/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -mutagen -isodate -google-api-python-client -musicbrainzngs -ujson -youtube_dl -pyacoustid -python-telegram-bot diff --git a/YouTubeMDBot/tests/converter.py b/YouTubeMDBot/tests/converter.py deleted file mode 100644 index 17aac27..0000000 --- a/YouTubeMDBot/tests/converter.py +++ /dev/null @@ -1,36 +0,0 @@ -import unittest -import mutagen - -from typing import Tuple -from io import BytesIO - -from YouTubeMDBot.tests.tagger import TaggerTest -from YouTubeMDBot.downloader import YouTubeDownloader -from YouTubeMDBot.audio import FFmpegMP3 -from YouTubeMDBot.audio import FFmpegOGG - - -class MyTestCase(TaggerTest): - def find_metadata(self, downloader: YouTubeDownloader) -> Tuple[BytesIO, bytes]: - io, data = super().find_metadata(downloader) - io.seek(0) - mp3 = FFmpegMP3(data=data, bitrate="96k") # downrate - ogg = FFmpegOGG(data=data, bitrate="256k") # uprate - - mp3.convert() - ogg.convert() - - mp3_container = BytesIO(mp3.get_output()) - ogg_container = BytesIO(ogg.get_output()) - - print(mp3.get_extra().decode("utf-8")) - print(ogg.get_extra().decode("utf-8")) - - print(mutagen.File(mp3_container).pprint()) - print(mutagen.File(ogg_container).pprint()) - - return io, data - - -if __name__ == '__main__': - unittest.main() diff --git a/YouTubeMDBot/tests/downloader.py b/YouTubeMDBot/tests/downloader.py deleted file mode 100755 index cb53f74..0000000 --- a/YouTubeMDBot/tests/downloader.py +++ /dev/null @@ -1,46 +0,0 @@ -import threading -import unittest -from time import sleep - -from YouTubeMDBot.downloader import YouTubeDownloader - - -class DownloadTest(unittest.TestCase): - _elements = 0 - _max = 0 - _lock = threading.Lock() - - def test_multithread_download(self): - yt1 = YouTubeDownloader(url="https://www.youtube.com/watch?v=Inm-N5rLUSI") - yt2 = YouTubeDownloader(url="https://www.youtube.com/watch?v=-_ZwpOdXXcA") - yt3 = YouTubeDownloader(url="https://www.youtube.com/watch?v=WOGWZD5iT10") - yt4 = YouTubeDownloader(url="https://www.youtube.com/watch?v=9HfoNUjw5u8") - t1 = threading.Thread(target=self.write_to_file, args=(yt1, "v1.m4a",)) - t2 = threading.Thread(target=self.write_to_file, args=(yt2, "v2.m4a",)) - t3 = threading.Thread(target=self.write_to_file, args=(yt3, "v3.m4a",)) - t4 = threading.Thread(target=self.write_to_file, args=(yt4, "v4.m4a",)) - - self._max = 4 - - t1.start() - t2.start() - t3.start() - t4.start() - - while self._elements < self._max: - sleep(1) - - def barrier(self): - with self._lock: - self._elements += 1 - - def write_to_file(self, yt: YouTubeDownloader, name: str): - _, data = yt.download() - print(name + " downloaded") - with open(name, "wb") as f: - f.write(data) - self.barrier() - - -if __name__ == '__main__': - unittest.main() diff --git a/YouTubeMDBot/tests/identifier.py b/YouTubeMDBot/tests/identifier.py deleted file mode 100755 index 747f4a0..0000000 --- a/YouTubeMDBot/tests/identifier.py +++ /dev/null @@ -1,106 +0,0 @@ -import threading -import unittest -from pprint import pprint -from time import sleep -from time import time -from typing import Tuple -from io import BytesIO - -from YouTubeMDBot.downloader import YouTubeDownloader -from YouTubeMDBot.metadata import YouTubeMetadataIdentifier - - -class IdentifierTest(unittest.TestCase): - lock = threading.Lock() - threads = 0 - max = 0 - song_info = {} - - def test_identification(self): - url = "https://www.youtube.com/watch?v=YQHsXMglC9A" - downloader = YouTubeDownloader(url=url) - audio, data = downloader.download() - with open("hello.m4a", "wb") as song: - song.write(data) - identifier = YouTubeMetadataIdentifier(audio=data, downloader=downloader) - - valid = identifier.identify_audio() - assert valid - print("{0} by {1} - score: {2} / 1\n" - "\thttps://musicbrainz.org/recording/{3}\n" - "\thttps://musicbrainz.org/release/{4}\n\n" - .format(identifier.title, identifier.artist, - identifier.score, - identifier.recording_id, identifier.release_id)) - with open("cover.jpg", "wb") as cover: - cover.write(identifier.cover) - - def test_multiple_download_identification(self): - yt1 = YouTubeDownloader(url="https://www.youtube.com/watch?v=Inm-N5rLUSI") - yt2 = YouTubeDownloader(url="https://www.youtube.com/watch?v=-_ZwpOdXXcA") - yt3 = YouTubeDownloader(url="https://www.youtube.com/watch?v=WOGWZD5iT10") - yt4 = YouTubeDownloader(url="https://www.youtube.com/watch?v=GfKV9KaNJXc") - yt5 = YouTubeDownloader(url="https://www.youtube.com/watch?v=DiItGE3eAyQ") - yt6 = YouTubeDownloader(url="https://www.youtube.com/watch?v=GuZzuQvv7uc") - - t1 = threading.Thread(target=self.find_metadata, args=(yt1,)) - t2 = threading.Thread(target=self.find_metadata, args=(yt2,)) - t3 = threading.Thread(target=self.find_metadata, args=(yt3,)) - t4 = threading.Thread(target=self.find_metadata, args=(yt4,)) - t5 = threading.Thread(target=self.find_metadata, args=(yt5,)) - t6 = threading.Thread(target=self.find_metadata, args=(yt6,)) - - self.max = 6 - - t1.start() - t2.start() - t3.start() - t4.start() - t5.start() - t6.start() - - while self.threads < self.max: - sleep(1) - - # pprint(self.song_info) - pprint("Finished") - - def barrier(self): - with self.lock: - self.threads += 1 - - def getThreads(self): - with self.lock: - return self.threads - - def find_metadata(self, downloader: YouTubeDownloader) -> Tuple[BytesIO, bytes]: - st_dl_t = time() - io, data = downloader.download() - f_dl_t = time() - print("Downloaded {} - elapsed time: {:.1f}s".format(downloader.get_url(), - f_dl_t - st_dl_t)) - identifier = YouTubeMetadataIdentifier(audio=data, downloader=downloader) - valid = identifier.identify_audio() - assert valid - self.song_info[downloader.get_url()] = { - "title": identifier.title, - "artist": identifier.artist, - "cover": identifier.cover - } - if not identifier.youtube_data: - self.song_info[downloader.get_url()]["score"] = identifier.score - self.song_info[downloader.get_url()]["record_id"] = \ - "https://musicbrainz.org/recording/{0}".format(identifier.recording_id) - self.song_info[downloader.get_url()]["release_id"] = \ - "https://musicbrainz.org/release/{0}".format(identifier.release_id) - self.song_info[downloader.get_url()]["album"] = identifier.album - else: - self.song_info[downloader.get_url()]["duration"] = identifier.duration - self.song_info[downloader.get_url()]["id"] = identifier.youtube_id - self.song_info[downloader.get_url()]["youtube_data"] = True - self.barrier() - return io, data - - -if __name__ == '__main__': - unittest.main() diff --git a/YouTubeMDBot/tests/song_search.py b/YouTubeMDBot/tests/song_search.py deleted file mode 100755 index c4afadb..0000000 --- a/YouTubeMDBot/tests/song_search.py +++ /dev/null @@ -1,25 +0,0 @@ -import unittest - -from YouTubeMDBot.api import YouTubeAPI -from YouTubeMDBot.api import YouTubeVideoData - - -class TestSearch(unittest.TestCase): - def test_search(self): - s = YouTubeAPI() - search: dict = s.search(term="test") - data = YouTubeVideoData(data=search, ignore_errors=True) - print("Title: {0}\n" - "Artist: {1}\n" - "Thumbnail: {2}\n" - "Duration: {3}\n" - "Views: {4}\n" - "Likes: {5}\n" - "Dislikes: {6}\n" - "Id: {7}".format(data.title, data.artist, data.thumbnail, - data.duration, data.views, data.likes, - data.dislikes, data.id)) - - -if __name__ == '__main__': - unittest.main() diff --git a/YouTubeMDBot/tests/tagger.py b/YouTubeMDBot/tests/tagger.py deleted file mode 100755 index 7d8ed02..0000000 --- a/YouTubeMDBot/tests/tagger.py +++ /dev/null @@ -1,44 +0,0 @@ -import unittest -import mutagen - -from typing import Tuple -from io import BytesIO - -from YouTubeMDBot.tests.identifier import IdentifierTest -from YouTubeMDBot.downloader import YouTubeDownloader -from YouTubeMDBot.metadata import AudioMetadata -from YouTubeMDBot.utils import youtube_utils - - -class TaggerTest(IdentifierTest): - def find_metadata(self, downloader: YouTubeDownloader) -> Tuple[BytesIO, bytes]: - io, data = super().find_metadata(downloader) - tagger = AudioMetadata(io) - url = downloader.get_url() - - tagger.set_title(super().song_info[url]["title"]) - tagger.set_artist(super().song_info[url]["artist"]) - tagger.set_cover(super().song_info[url]["cover"]) - extra = ["YouTube URL: " + url] - if not super().song_info[url].get("youtube_data"): - tagger.set_album(super().song_info[url]["album"]) - extra.append("MusicBrainz Record ID: " + super().song_info[url][ - "record_id"]) - extra.append("MusicBrainz Release ID: " + super().song_info[url][ - "release_id"]) - tagger.set_extras(extra) - else: - tagger.set_extras(["YouTube ID: {}".format(super().song_info[url]["id"])]) - yid = youtube_utils.get_yt_video_id(url) - rs = tagger.save() - rs.seek(0) - print(mutagen.File(rs).pprint()) - rs.seek(0) - with open(yid + ".m4a", "wb") as f: - f.write(rs.read()) - rs.seek(0) - return rs, rs.read() - - -if __name__ == '__main__': - unittest.main() diff --git a/YouTubeMDBot/utils/__init__.py b/YouTubeMDBot/utils/__init__.py deleted file mode 100755 index 9fe1f3e..0000000 --- a/YouTubeMDBot/utils/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -from ..utils.youtube_utils import get_yt_video_id diff --git a/YouTubeMDBot/utils/youtube_utils.py b/YouTubeMDBot/utils/youtube_utils.py deleted file mode 100755 index 7d66274..0000000 --- a/YouTubeMDBot/utils/youtube_utils.py +++ /dev/null @@ -1,52 +0,0 @@ -# YouTubeMDBot -# Copyright (C) 2019 - Javinator9889 -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -def get_yt_video_id(url: str) -> str: - # initial version: http://stackoverflow.com/a/7936523/617185 \ - # by Mikhail Kashkin(http://stackoverflow.com/users/85739/mikhail-kashkin) - """Returns Video_ID extracting from the given url of Youtube - - Examples of URLs: - Valid: - 'http://youtu.be/_lOT2p_FCvA', - 'www.youtube.com/watch?v=_lOT2p_FCvA&feature=feedu', - 'http://www.youtube.com/embed/_lOT2p_FCvA', - 'http://www.youtube.com/v/_lOT2p_FCvA?version=3&hl=en_US', - 'https://www.youtube.com/watch?v=rTHlyTphWP0&index=6&list=PLjeDyYvG6-40qawYNR4juzvSOg-ezZ2a6', - 'youtube.com/watch?v=_lOT2p_FCvA', - - Invalid: - 'youtu.be/watch?v=_lOT2p_FCvA', - """ - - from urllib.parse import urlparse - from urllib.parse import parse_qs - - if url.startswith(('youtu', 'www')): - url = 'http://' + url - - query = urlparse(url) - - if 'youtube' in query.hostname: - if query.path == '/watch': - return parse_qs(query.query)['v'][0] - elif query.path.startswith(('/embed/', '/v/')): - return query.path.split('/')[2] - elif 'youtu.be' in query.hostname: - return query.path[1:] - else: - raise ValueError