mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-14 21:38:38 +00:00
Merge branch 'main' of https://github.com/migatu/conjurer
This commit is contained in:
@@ -1,37 +1,75 @@
|
|||||||
from logging import handlers
|
"""
|
||||||
import re
|
This module contains the implementation of the Librarian class and related functions for searching and refining queries.
|
||||||
|
|
||||||
|
Classes:
|
||||||
|
- Librarian: Represents a librarian object that performs search and refinement operations on queries.
|
||||||
|
|
||||||
|
Functions:
|
||||||
|
- flask_debug: Starts a Flask application in debug mode without using the reloader.
|
||||||
|
- waitress_run: Serves the Flask application using the Waitress WSGI server.
|
||||||
|
- BackgroundTaskSearch: Represents a background task for running the Librarian object asynchronously.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import netrc
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from flask import Flask, jsonify, request
|
|
||||||
import netrc
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
|
from logging import handlers
|
||||||
|
from queue import Queue
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import scrape_bot
|
||||||
|
import search_bot
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
from habanero import Crossref
|
from habanero import Crossref
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
from queue import Queue
|
|
||||||
import search_bot
|
|
||||||
import scrape_bot
|
|
||||||
import requests
|
|
||||||
NETRC_FILE = r"C:\Users\Activcom.pl\.netrc"
|
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
NETRC_FILE = r"C:\Users\Activcom.pl\.netrc"
|
||||||
HOST_ADDRESS = "192.168.1.192"
|
HOST_ADDRESS = "192.168.1.192"
|
||||||
PORT_ADDRESS = 5001
|
PORT_ADDRESS = 5001
|
||||||
MAIN_BOT_ADDRESS = "http://192.168.1.191:5000"
|
MAIN_BOT_ADDRESS = "http://192.168.1.191:5000"
|
||||||
SEND_RESULTS = "/conjurer"
|
SEND_RESULTS = "/conjurer"
|
||||||
BDSM_UUID_TEST = '96b7f85a-1142-4908-8986-62a2ea25a147'
|
BDSM_UUID_TEST = "96b7f85a-1142-4908-8986-62a2ea25a147"
|
||||||
MAX_CR_RESULTS = 150000
|
MAX_CR_RESULTS = 150000
|
||||||
ENCODING = "utf-8"
|
ENCODING = "utf-8"
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
librarian_queue = Queue()
|
librarian_queue = Queue()
|
||||||
librarian_list = []
|
librarian_list = []
|
||||||
|
|
||||||
class Librarian(object):
|
|
||||||
|
|
||||||
def __init__(self, app, query, uuid) -> None:
|
class Librarian(object):
|
||||||
|
def __init__(self, _app, query, uuid) -> None:
|
||||||
|
"""
|
||||||
|
Initializes a Librarian object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- _app: The Flask application object.
|
||||||
|
- query: The query to be searched.
|
||||||
|
- uuid: The unique identifier for the search.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- cr: The Crossref object for performing the search.
|
||||||
|
- query: The query to be searched.
|
||||||
|
- uuid: The unique identifier for the search.
|
||||||
|
- limit: The maximum number of search results to fetch.
|
||||||
|
- fetched: The number of search results fetched so far.
|
||||||
|
- hit: The number of search results that match the refinement criteria.
|
||||||
|
- total: The total number of search results.
|
||||||
|
- app: The Flask application object.
|
||||||
|
- live_results: A list to store live search results.
|
||||||
|
- final_result: A list to store the final refined search results.
|
||||||
|
- not_in_db: A list to store search results that are not in the local database.
|
||||||
|
- search_result_from_cr: A dictionary to store the search results from Crossref.
|
||||||
|
- done: A flag indicating if the search is done.
|
||||||
|
"""
|
||||||
netrc_mod = netrc.netrc(NETRC_FILE)
|
netrc_mod = netrc.netrc(NETRC_FILE)
|
||||||
authTokens = netrc_mod.authenticators("crossref")
|
authTokens = netrc_mod.authenticators("crossref")
|
||||||
self.cr = Crossref(mailto=authTokens[0])
|
self.cr = Crossref(mailto=authTokens[0])
|
||||||
@@ -41,7 +79,7 @@ class Librarian(object):
|
|||||||
self.fetched = 0
|
self.fetched = 0
|
||||||
self.hit = 0
|
self.hit = 0
|
||||||
self.total = 0
|
self.total = 0
|
||||||
self.app = app
|
self.app = _app
|
||||||
self.live_results = []
|
self.live_results = []
|
||||||
self.final_result = []
|
self.final_result = []
|
||||||
self.not_in_db = []
|
self.not_in_db = []
|
||||||
@@ -49,25 +87,37 @@ class Librarian(object):
|
|||||||
self.done = False
|
self.done = False
|
||||||
|
|
||||||
async def search_crossref(self, query):
|
async def search_crossref(self, query):
|
||||||
|
"""
|
||||||
|
Performs a search on Crossref for the given query.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- query: The query to be searched.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The search result from Crossref.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- None.
|
||||||
|
"""
|
||||||
self.app.logger.info("STARTED SEARCH")
|
self.app.logger.info("STARTED SEARCH")
|
||||||
result = self.cr.works(query = query, limit = 1000)
|
result = self.cr.works(query=query, limit=1000)
|
||||||
self.search_result_from_cr.update(result)
|
self.search_result_from_cr.update(result)
|
||||||
self.total = result['message']['total-results']
|
self.total = result["message"]["total-results"]
|
||||||
self.fetched += len(result['message']['items'])
|
self.fetched += len(result["message"]["items"])
|
||||||
self.app.logger.info (self.total)
|
self.app.logger.info(self.total)
|
||||||
self.app.logger.info(self.fetched)
|
self.app.logger.info(self.fetched)
|
||||||
while self.total > self.fetched and self.limit > self.fetched:
|
while self.total > self.fetched and self.limit > self.fetched:
|
||||||
result = self.cr.works(query = query, limit = 1000)
|
result = self.cr.works(query=query, limit=1000)
|
||||||
self.search_result_from_cr.update(result)
|
self.search_result_from_cr.update(result)
|
||||||
self.total = result['message']['total-results']
|
self.total = result["message"]["total-results"]
|
||||||
self.fetched += len(result['message']['items'])
|
self.fetched += len(result["message"]["items"])
|
||||||
self.app.logger.info (self.total)
|
self.app.logger.info(self.total)
|
||||||
self.app.logger.info(self.fetched)
|
self.app.logger.info(self.fetched)
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.app.logger.info("CROSSREF DONE")
|
self.app.logger.info("CROSSREF DONE")
|
||||||
#TODO: add to sql with UUID
|
# TODO: add to sql with UUID
|
||||||
#sql row - title, doi, author, uuid, last retrieved, is_available
|
# sql row - title, doi, author, uuid, last retrieved, is_available
|
||||||
|
|
||||||
with open("rr_results.json", "r+", encoding="utf-8") as data_file:
|
with open("rr_results.json", "r+", encoding="utf-8") as data_file:
|
||||||
# First we load existing data into a dict.
|
# First we load existing data into a dict.
|
||||||
try:
|
try:
|
||||||
@@ -76,18 +126,34 @@ class Librarian(object):
|
|||||||
file_data = {}
|
file_data = {}
|
||||||
summarized_results = []
|
summarized_results = []
|
||||||
self.app.logger.info("REFINE: Removing all derived works from the list")
|
self.app.logger.info("REFINE: Removing all derived works from the list")
|
||||||
for item in self.search_result_from_cr['message']['items']:
|
for item in self.search_result_from_cr["message"]["items"]:
|
||||||
container = "NO"
|
container = "NO"
|
||||||
if "container-title" in item:
|
if "container-title" in item:
|
||||||
container = "YES"
|
container = "YES"
|
||||||
else:
|
else:
|
||||||
summarized_results.append({"DOI" : item['DOI'], "title": item['title'] if 'title' in item else None,"type": item['type'] if 'type' in item else None, "container": container})
|
summarized_results.append(
|
||||||
result = {self.uuid : {"total_results": self.search_result_from_cr['message']['total-results'], "on_page" : 1, "summary":summarized_results, "results":self.search_result_from_cr['message']['items']}}
|
{
|
||||||
|
"DOI": item["DOI"],
|
||||||
|
"title": item["title"] if "title" in item else None,
|
||||||
|
"type": item["type"] if "type" in item else None,
|
||||||
|
"container": container,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = {
|
||||||
|
self.uuid: {
|
||||||
|
"total_results": self.search_result_from_cr["message"][
|
||||||
|
"total-results"
|
||||||
|
],
|
||||||
|
"on_page": 1,
|
||||||
|
"summary": summarized_results,
|
||||||
|
"results": self.search_result_from_cr["message"]["items"],
|
||||||
|
}
|
||||||
|
}
|
||||||
if file_data:
|
if file_data:
|
||||||
file_data.update(result)
|
file_data.update(result)
|
||||||
else:
|
else:
|
||||||
file_data = result
|
file_data = result
|
||||||
|
|
||||||
self.app.logger.info("REFINE: Dumping to file")
|
self.app.logger.info("REFINE: Dumping to file")
|
||||||
data_file.truncate(0)
|
data_file.truncate(0)
|
||||||
data_file.seek(0)
|
data_file.seek(0)
|
||||||
@@ -95,12 +161,38 @@ class Librarian(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
async def check_dois_in_local_db(self, doi):
|
async def check_dois_in_local_db(self, doi):
|
||||||
|
"""
|
||||||
|
Checks if the given DOI exists in the local database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- doi: The DOI to be checked.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The search result from the backend app.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- None.
|
||||||
|
"""
|
||||||
self.app.logger.info("REFINE: Running search in the backend app")
|
self.app.logger.info("REFINE: Running search in the backend app")
|
||||||
coro = asyncio.to_thread(search_bot.search_for_doi, doi, self.live_results, self.app.logger)
|
coro = asyncio.to_thread(
|
||||||
|
search_bot.search_for_doi, doi, self.live_results, self.app.logger
|
||||||
|
)
|
||||||
result = await coro
|
result = await coro
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def check_if_exists_brute_force(self, page_url):
|
async def check_if_exists_brute_force(self, page_url):
|
||||||
|
"""
|
||||||
|
Checks if the given page URL exists using brute force method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- page_url: The URL of the page to be checked.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- exists: A boolean indicating if the page exists.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- ValueError: If the URL does not start with 'http:' or 'https:'.
|
||||||
|
"""
|
||||||
self.app.logger.error("REFINE: Brute force search!")
|
self.app.logger.error("REFINE: Brute force search!")
|
||||||
if not page_url.startswith(("http:", "https:")):
|
if not page_url.startswith(("http:", "https:")):
|
||||||
raise ValueError("URL must start with 'http:' or 'https:'")
|
raise ValueError("URL must start with 'http:' or 'https:'")
|
||||||
@@ -109,33 +201,68 @@ class Librarian(object):
|
|||||||
data = response.read()
|
data = response.read()
|
||||||
text = data.decode("utf-8")
|
text = data.decode("utf-8")
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
if re.match(r".*Unfortunately, Sci-Hub doesn't have the requested document.*", line):
|
if re.match(
|
||||||
|
r".*Unfortunately, Sci-Hub doesn't have the requested document.*",
|
||||||
|
line,
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
if m := re.match(r".*<embed type=\"application.pdf\"\s*src=\"(.*\.pdf)",line):
|
if m := re.match(
|
||||||
|
r".*<embed type=\"application.pdf\"\s*src=\"(.*\.pdf)", line
|
||||||
|
):
|
||||||
direct_download_link = "https:" + str(m.group(1))
|
direct_download_link = "https:" + str(m.group(1))
|
||||||
self.app.logger.info (direct_download_link)
|
self.app.logger.info(direct_download_link)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def check_if_exists(self, doi, brute_force = False):
|
async def check_if_exists(self, doi, brute_force=False):
|
||||||
|
"""
|
||||||
|
Checks if the given DOI exists.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- doi: The DOI to be checked.
|
||||||
|
- brute_force: A flag indicating if brute force method should be used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The search result.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- None.
|
||||||
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
result = await self.check_dois_in_local_db(doi)
|
result = await self.check_dois_in_local_db(doi)
|
||||||
if brute_force and not result:
|
if brute_force and not result:
|
||||||
for item in doi:
|
for item in doi:
|
||||||
item_link = "https://sci-hub.se/" + item[0]
|
item_link = "https://sci-hub.se/" + item[0]
|
||||||
tmp = {"DOI" : item["DOI"], "exists" : await self.check_if_exists_brute_force(item_link), "data": item}
|
tmp = {
|
||||||
|
"DOI": item["DOI"],
|
||||||
|
"exists": await self.check_if_exists_brute_force(item_link),
|
||||||
|
"data": item,
|
||||||
|
}
|
||||||
result.update(tmp)
|
result.update(tmp)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def refine_query(self, unrefined_reqult, brute_force = False):
|
async def refine_query(self, unrefined_result):
|
||||||
|
"""
|
||||||
|
Refines the search query based on the unrefined search result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- unrefined_result: The unrefined search result.
|
||||||
|
- brute_force: A flag indicating if brute force method should be used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- final_result: The final refined search result.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- None.
|
||||||
|
"""
|
||||||
temp = []
|
temp = []
|
||||||
refined_result = []
|
refined_result = []
|
||||||
for key in unrefined_reqult:
|
for key in unrefined_result:
|
||||||
self.app.logger.info("KEY:")
|
self.app.logger.info("KEY:")
|
||||||
self.app.logger.info(key)
|
self.app.logger.info(key)
|
||||||
|
|
||||||
for item in unrefined_reqult[self.uuid]["summary"]:
|
for item in unrefined_result[self.uuid]["summary"]:
|
||||||
if item["container"] == "NO" and item["title"]:
|
if item["container"] == "NO" and item["title"]:
|
||||||
refined_result.append(item)
|
refined_result.append(item)
|
||||||
|
|
||||||
for item in refined_result:
|
for item in refined_result:
|
||||||
@@ -150,83 +277,142 @@ class Librarian(object):
|
|||||||
self.not_in_db.append(item["data"])
|
self.not_in_db.append(item["data"])
|
||||||
self.hit = len(self.final_result)
|
self.hit = len(self.final_result)
|
||||||
return self.final_result
|
return self.final_result
|
||||||
#TODO: DOłożyć sprawdzenie czy w rafinowanym pliku już nie mamy częściowego wyniku
|
|
||||||
async def answer_query(self):
|
async def answer_query(self):
|
||||||
#TODO: DOłożyć sprawdzenie czy w rafinowanym pliku już nie mamy częściowego wyniku tutaj bo zanim w ogóle otworzymy nierafinowany
|
"""
|
||||||
#Search sql for UUID
|
Answers the search query.
|
||||||
#SQL SELECT * FROM RESULTS WHERE UUID = uuid
|
|
||||||
|
Args:
|
||||||
|
- None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- result: The search result.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- None.
|
||||||
|
"""
|
||||||
self.app.logger.info(f"Search started {self.uuid}")
|
self.app.logger.info(f"Search started {self.uuid}")
|
||||||
database = None
|
database = None
|
||||||
query = self.query
|
query = self.query
|
||||||
with open("cr_results.json", "r+", encoding="utf-8") as cr_file:
|
with open("cr_results.json", "r+", encoding="utf-8") as cr_file:
|
||||||
#tu logika komunikacji z SQL i cała logika związana z wyszukaniem
|
|
||||||
#ale tymczasowo plik
|
|
||||||
try:
|
try:
|
||||||
database = json.load(cr_file)
|
database = json.load(cr_file)
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
#TODO: Zwracać i final_result i not_in_db. Not in db_do pliku - i do ręcznego przeglądania potem.
|
|
||||||
if database:
|
if database:
|
||||||
for item in database.keys():
|
for item in database.keys():
|
||||||
if item in self.uuid:
|
if item in self.uuid:
|
||||||
self.result = await self.refine_query(database)
|
self.result = await self.refine_query(database)
|
||||||
temp = {}
|
temp = {}
|
||||||
for item in self.result:
|
for item in self.result:
|
||||||
temp[item['DOI']] = {"Title" : item['title'], "type": item['type']}
|
temp[item["DOI"]] = {
|
||||||
|
"Title": item["title"],
|
||||||
|
"type": item["type"],
|
||||||
|
}
|
||||||
self.result = temp
|
self.result = temp
|
||||||
temp2 = {}
|
temp2 = {}
|
||||||
for item in self.not_in_db:
|
for item in self.not_in_db:
|
||||||
temp2[item['DOI']] = {"Title" : item['title'], "type": item['type']}
|
temp2[item["DOI"]] = {
|
||||||
|
"Title": item["title"],
|
||||||
|
"type": item["type"],
|
||||||
|
}
|
||||||
self.not_in_db = temp2
|
self.not_in_db = temp2
|
||||||
return self.result
|
return self.result
|
||||||
answer = await self.search_crossref(query=query)
|
answer = await self.search_crossref(query=query)
|
||||||
database.update(answer)
|
database.update(answer)
|
||||||
cr_file.truncate(0)
|
cr_file.truncate(0)
|
||||||
cr_file.seek(0)
|
cr_file.seek(0)
|
||||||
json.dump(database,cr_file)
|
json.dump(database, cr_file)
|
||||||
self.result = await self.refine_query(answer)
|
self.result = await self.refine_query(answer)
|
||||||
else:
|
else:
|
||||||
answer = await self.search_crossref(query=query)
|
answer = await self.search_crossref(query=query)
|
||||||
database = answer
|
database = answer
|
||||||
cr_file.truncate(0)
|
cr_file.truncate(0)
|
||||||
cr_file.seek(0)
|
cr_file.seek(0)
|
||||||
json.dump(database,cr_file)
|
json.dump(database, cr_file)
|
||||||
self.result = await self.refine_query(answer)
|
self.result = await self.refine_query(answer)
|
||||||
self.app.logger.info("Refined")
|
self.app.logger.info("Refined")
|
||||||
self.done = True
|
self.done = True
|
||||||
temp = {}
|
temp = {}
|
||||||
for item in self.result:
|
for item in self.result:
|
||||||
temp[item['DOI']] = {"Title" : item['title'], "type": item['type']}
|
temp[item["DOI"]] = {"Title": item["title"], "type": item["type"]}
|
||||||
self.result = temp
|
self.result = temp
|
||||||
temp2 = {}
|
temp2 = {}
|
||||||
for item in self.not_in_db:
|
for item in self.not_in_db:
|
||||||
temp2[item['DOI']] = {"Title" : item['title'], "type": item['type']}
|
temp2[item["DOI"]] = {"Title": item["title"], "type": item["type"]}
|
||||||
self.not_in_db = temp2
|
self.not_in_db = temp2
|
||||||
return self.result
|
return self.result
|
||||||
|
|
||||||
#============================= FLASK INTERNALS===============================
|
|
||||||
|
# ============================= FLASK INTERNALS===============================
|
||||||
|
|
||||||
|
|
||||||
def flask_debug():
|
def flask_debug():
|
||||||
"""
|
"""
|
||||||
The `flask_debug` function starts a Flask application in debug mode without using the reloader.
|
Starts a Flask application in debug mode without using the reloader.
|
||||||
Do not use for production for fucks sake.
|
|
||||||
|
Args:
|
||||||
|
- None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- None.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- None.
|
||||||
"""
|
"""
|
||||||
# trunk-ignore(bandit/B201)
|
# trunk-ignore(bandit/B201)
|
||||||
app.run(debug=True, use_reloader=False, host=HOST_ADDRESS, port=PORT_ADDRESS)
|
app.run(debug=True, use_reloader=False, host=HOST_ADDRESS, port=PORT_ADDRESS)
|
||||||
|
|
||||||
|
|
||||||
def waitress_run():
|
def waitress_run():
|
||||||
"""
|
"""
|
||||||
The `waitress_run` function serves the `app` on host "0.0.0.0"
|
Serves the Flask application using the Waitress WSGI server.
|
||||||
and port 5000 using the Waitress WSGI server.
|
|
||||||
|
Args:
|
||||||
|
- None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- None.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- None.
|
||||||
"""
|
"""
|
||||||
serve(app, host=HOST_ADDRESS, port=PORT_ADDRESS)
|
serve(app, host=HOST_ADDRESS, port=PORT_ADDRESS)
|
||||||
|
|
||||||
|
|
||||||
class BackgroundTaskSearch(threading.Thread):
|
class BackgroundTaskSearch(threading.Thread):
|
||||||
|
"""
|
||||||
|
A background task for searching and saving results to files.
|
||||||
|
|
||||||
|
This class extends the `threading.Thread` class and is responsible for running
|
||||||
|
the search task in the background. It retrieves queries from a queue, performs
|
||||||
|
the search, and saves the results to files.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
app (App): The application instance.
|
||||||
|
"""
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
loop = asyncio.new_event_loop() # loop = asyncio.get_event_loop()
|
"""
|
||||||
|
Run the background task.
|
||||||
|
|
||||||
|
This method is called when the thread is started. It creates a new event loop,
|
||||||
|
runs the `_run` method, and closes the event loop.
|
||||||
|
"""
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
loop.run_until_complete(self._run())
|
loop.run_until_complete(self._run())
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
|
"""
|
||||||
|
Perform the search task.
|
||||||
|
|
||||||
|
This method is an asynchronous coroutine that runs in a loop. It retrieves a
|
||||||
|
librarian from the queue, answers the query, and saves the results to files.
|
||||||
|
It also sends the results to a remote server.
|
||||||
|
|
||||||
|
The search task continues running indefinitely until the thread is stopped.
|
||||||
|
"""
|
||||||
while True:
|
while True:
|
||||||
database = None
|
database = None
|
||||||
ndb_database = None
|
ndb_database = None
|
||||||
@@ -235,9 +421,11 @@ class BackgroundTaskSearch(threading.Thread):
|
|||||||
result = await librarian.answer_query()
|
result = await librarian.answer_query()
|
||||||
result = {librarian.uuid: result}
|
result = {librarian.uuid: result}
|
||||||
self.app.logger.info("Saving to file")
|
self.app.logger.info("Saving to file")
|
||||||
|
|
||||||
|
# Save results to "not_in_db.json" file
|
||||||
with open("not_in_db.json", "r+", encoding="utf-8") as ndb_file:
|
with open("not_in_db.json", "r+", encoding="utf-8") as ndb_file:
|
||||||
try:
|
try:
|
||||||
ndb_database = json.load(ndb_file)
|
ndb_database = json.load(ndb_file)
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
if ndb_database:
|
if ndb_database:
|
||||||
@@ -248,9 +436,10 @@ class BackgroundTaskSearch(threading.Thread):
|
|||||||
ndb_file.seek(0)
|
ndb_file.seek(0)
|
||||||
json.dump(ndb_database, ndb_file)
|
json.dump(ndb_database, ndb_file)
|
||||||
|
|
||||||
|
# Save results to "s_results.json" file
|
||||||
with open("s_results.json", "r+", encoding="utf-8") as s_file:
|
with open("s_results.json", "r+", encoding="utf-8") as s_file:
|
||||||
try:
|
try:
|
||||||
database = json.load(s_file)
|
database = json.load(s_file)
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
if database:
|
if database:
|
||||||
@@ -267,20 +456,31 @@ class BackgroundTaskSearch(threading.Thread):
|
|||||||
|
|
||||||
self.app.logger.info(result)
|
self.app.logger.info(result)
|
||||||
coroutine = asyncio.to_thread(
|
coroutine = asyncio.to_thread(
|
||||||
requests.post,
|
requests.post,
|
||||||
f"{MAIN_BOT_ADDRESS}{SEND_RESULTS}",
|
f"{MAIN_BOT_ADDRESS}{SEND_RESULTS}",
|
||||||
json=result,
|
json=result,
|
||||||
timeout=360,
|
timeout=360,
|
||||||
)
|
)
|
||||||
self.app.logger.info("SENT")
|
self.app.logger.info("SENT")
|
||||||
result = await coroutine
|
result = await coroutine
|
||||||
self.app.logger.info(result.status_code)
|
self.app.logger.info(result.status_code)
|
||||||
self.app.logger.info("SEND CONFIRMED")
|
self.app.logger.info("SEND CONFIRMED")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
#==================================SERVER ROUTES==========================================
|
|
||||||
|
# ==================================SERVER ROUTES==========================================
|
||||||
@app.route("/query", methods=["POST"])
|
@app.route("/query", methods=["POST"])
|
||||||
async def query_database():
|
async def query_database():
|
||||||
|
"""
|
||||||
|
Endpoint for querying the database.
|
||||||
|
|
||||||
|
This function receives a POST request containing a JSON payload with a query and a UUID.
|
||||||
|
It creates a Librarian object with the query and UUID, and adds it to the librarian_queue and librarian_list.
|
||||||
|
Finally, it returns a JSON response indicating the success of the operation, along with the query, UUID, and the current size of the librarian_queue.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: A tuple containing a JSON response and a status code.
|
||||||
|
"""
|
||||||
record = json.loads(request.data)
|
record = json.loads(request.data)
|
||||||
app.logger.info(record)
|
app.logger.info(record)
|
||||||
app.logger.info(record["query"])
|
app.logger.info(record["query"])
|
||||||
@@ -296,8 +496,15 @@ async def query_database():
|
|||||||
)
|
)
|
||||||
return return_data
|
return return_data
|
||||||
|
|
||||||
|
|
||||||
@app.route("/get_partial_result", methods=["POST"])
|
@app.route("/get_partial_result", methods=["POST"])
|
||||||
async def get_partial():
|
async def get_partial():
|
||||||
|
"""
|
||||||
|
Retrieves the partial result for a given UUID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A JSON response containing the partial result.
|
||||||
|
"""
|
||||||
record = json.loads(request.data)
|
record = json.loads(request.data)
|
||||||
app.logger.info(record)
|
app.logger.info(record)
|
||||||
app.logger.info(record["UUID"])
|
app.logger.info(record["UUID"])
|
||||||
@@ -311,30 +518,31 @@ async def get_partial():
|
|||||||
)
|
)
|
||||||
return return_data
|
return return_data
|
||||||
|
|
||||||
#=======================================MAIN===================================================
|
|
||||||
|
# =======================================MAIN===================================================
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.logger.setLevel(logging.DEBUG)
|
app.logger.setLevel(logging.DEBUG)
|
||||||
h1 = handlers.RotatingFileHandler(
|
h1 = handlers.RotatingFileHandler(
|
||||||
filename="E:\\logs\\librarian.log",
|
filename="E:\\logs\\librarian.log",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
mode="a",
|
mode="a",
|
||||||
maxBytes=6 * 1024 * 1024,
|
maxBytes=6 * 1024 * 1024,
|
||||||
backupCount=6,
|
backupCount=6,
|
||||||
)
|
)
|
||||||
|
|
||||||
app.logger.addHandler(h1)
|
app.logger.addHandler(h1)
|
||||||
threads = []
|
threads = []
|
||||||
threads.append(threading.Thread(target=waitress_run))
|
threads.append(threading.Thread(target=waitress_run))
|
||||||
#threads.append(threading.Thread(target=flask_debug))
|
# threads.append(threading.Thread(target=flask_debug))
|
||||||
bgtask = BackgroundTaskSearch()
|
bgtask = BackgroundTaskSearch()
|
||||||
bgtask.app = app
|
bgtask.app = app
|
||||||
threads.append(bgtask)
|
threads.append(bgtask)
|
||||||
threads.append(threading.Thread(target = scrape_bot.scraper, args=(app.logger,)))
|
threads.append(threading.Thread(target=scrape_bot.scraper, args=(app.logger,)))
|
||||||
i = 0
|
i = 0
|
||||||
for worker in threads:
|
for worker in threads:
|
||||||
try:
|
try:
|
||||||
app.logger.info(f"App number: {i}")
|
app.logger.info("App number: %s", i)
|
||||||
i+=1
|
i += 1
|
||||||
worker.start()
|
worker.start()
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
app.logger.error("Exploded")
|
app.logger.error("Exploded")
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ if platform in ("linux", "linux2"):
|
|||||||
LOGSTORE = "/home/pi/RetroPie/logs/"
|
LOGSTORE = "/home/pi/RetroPie/logs/"
|
||||||
ENCODING = "utf-8"
|
ENCODING = "utf-8"
|
||||||
MUSIC_FOLDER = "/home/pi/RetroPie/mp3/"
|
MUSIC_FOLDER = "/home/pi/RetroPie/mp3/"
|
||||||
|
PRIORITY_FOLDER = "/home/pi/RetroPie/mp3/Magiczne i chuj/"
|
||||||
|
|
||||||
|
|
||||||
music_file_list = []
|
music_file_list = []
|
||||||
|
priority_list = []
|
||||||
|
|
||||||
|
|
||||||
def create_playlist():
|
def create_playlist():
|
||||||
@@ -63,6 +65,16 @@ def create_playlist():
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
with open("/home/pi/Conjurer/prio_playlist.json", "r+", encoding="utf-8") as r_file:
|
||||||
|
with open("/home/pi/Conjurer/hit.playlist", "w", encoding="utf-8") as w_file:
|
||||||
|
try:
|
||||||
|
playlist = json.load(r_file)
|
||||||
|
for item in playlist:
|
||||||
|
w_file.write(item)
|
||||||
|
w_file.write("\n")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def rescan():
|
def rescan():
|
||||||
"""
|
"""
|
||||||
@@ -70,17 +82,30 @@ def rescan():
|
|||||||
and adds them to a list of music files.
|
and adds them to a list of music files.
|
||||||
"""
|
"""
|
||||||
logging.info("Rescan triggered")
|
logging.info("Rescan triggered")
|
||||||
|
|
||||||
for mp3_item in Path.glob(Path(MUSIC_FOLDER), "**/*.mp3"):
|
for mp3_item in Path.glob(Path(MUSIC_FOLDER), "**/*.mp3"):
|
||||||
temp_music_file = mp3_item.as_posix()
|
temp_music_file = mp3_item.as_posix()
|
||||||
if platform == "win32":
|
if platform == "win32":
|
||||||
temp_music_file = temp_music_file.replace("/", "\\")
|
temp_music_file = temp_music_file.replace("/", "\\")
|
||||||
music_file_list.append(temp_music_file)
|
music_file_list.append(temp_music_file)
|
||||||
|
|
||||||
|
for mp3_item in Path.glob(Path(PRIORITY_FOLDER), "**/*.mp3"):
|
||||||
|
temp_music_file = mp3_item.as_posix()
|
||||||
|
if platform == "win32":
|
||||||
|
temp_music_file = temp_music_file.replace("/", "\\")
|
||||||
|
priority_list.append(temp_music_file)
|
||||||
|
|
||||||
with open("/home/pi/Conjurer/playlist.json", "r+", encoding="utf-8") as s_file:
|
with open("/home/pi/Conjurer/playlist.json", "r+", encoding="utf-8") as s_file:
|
||||||
database = music_file_list
|
database = music_file_list
|
||||||
# self.app.logger.info("DUMPING DATA")
|
# self.app.logger.info("DUMPING DATA")
|
||||||
s_file.truncate(0)
|
s_file.truncate(0)
|
||||||
s_file.seek(0)
|
s_file.seek(0)
|
||||||
json.dump(database, s_file)
|
json.dump(database, s_file)
|
||||||
|
with open("/home/pi/Conjurer/prio_playlist.json", "r+", encoding="utf-8") as s_file:
|
||||||
|
# self.app.logger.info("DUMPING DATA")
|
||||||
|
s_file.truncate(0)
|
||||||
|
s_file.seek(0)
|
||||||
|
json.dump(priority_list, s_file)
|
||||||
create_playlist()
|
create_playlist()
|
||||||
|
|
||||||
|
|
||||||
@@ -202,7 +227,9 @@ def wyszukaj(word_list, how_many, _logger=None, return_to_bot=True):
|
|||||||
return_list.append(search_weight[itr])
|
return_list.append(search_weight[itr])
|
||||||
if not return_to_bot:
|
if not return_to_bot:
|
||||||
with open(
|
with open(
|
||||||
"/home/pi/Conjurer/priority_queue.playlist", "r+", encoding="utf-8"
|
"/home/pi/Conjurer/priority_queue.playlist",
|
||||||
|
"r+",
|
||||||
|
encoding="utf-8",
|
||||||
) as s_file:
|
) as s_file:
|
||||||
s_file.write(search_weight[itr][1])
|
s_file.write(search_weight[itr][1])
|
||||||
break
|
break
|
||||||
@@ -214,9 +241,8 @@ def wyszukaj(word_list, how_many, _logger=None, return_to_bot=True):
|
|||||||
if not return_to_bot:
|
if not return_to_bot:
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
"/home/pi/Conjurer/priority_queue.playlist", "r+", encoding="utf-8"
|
"/home/pi/Conjurer/priority_queue.playlist", "a", encoding="utf-8"
|
||||||
) as s_file:
|
) as s_file:
|
||||||
s_file.truncate(0)
|
|
||||||
for item in return_list:
|
for item in return_list:
|
||||||
s_file.write(item[1] + "\n")
|
s_file.write(item[1] + "\n")
|
||||||
return return_list
|
return return_list
|
||||||
@@ -253,24 +279,36 @@ def remove_characters(string, character):
|
|||||||
@app.route("/stream", methods=["GET"])
|
@app.route("/stream", methods=["GET"])
|
||||||
def stream_music():
|
def stream_music():
|
||||||
"""
|
"""
|
||||||
The function `get_music_list` returns a JSON object containing a list of music files.
|
The function `stream_music` is a route handler for the "/stream" endpoint.
|
||||||
:return: A JSON response containing a key "music_file_list" with the value of the variable
|
It returns a JSON response containing a key "music_file_list" with the value of the variable `music_file_list`.
|
||||||
`music_file_list`.
|
|
||||||
|
:return: A JSON response containing a key "music_file_list" with the value of the variable `music_file_list`.
|
||||||
"""
|
"""
|
||||||
return send_from_directory("/tmp/hls", "stream.m3u8")
|
return send_from_directory("/tmp/hls", "stream.m3u8")
|
||||||
|
|
||||||
@app.route('/<string:file_name>')
|
|
||||||
|
@app.route("/<string:file_name>")
|
||||||
def stream(file_name):
|
def stream(file_name):
|
||||||
video_dir = '/tmp/hls'
|
"""
|
||||||
|
Stream the specified file from the video directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_name (str): The name of the file to be streamed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response: The response containing the streamed file.
|
||||||
|
"""
|
||||||
|
video_dir = "/tmp/hls"
|
||||||
return send_from_directory(video_dir, file_name)
|
return send_from_directory(video_dir, file_name)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/stream_mp3", methods=["GET"])
|
@app.route("/stream_mp3", methods=["GET"])
|
||||||
def stream_music_mp3():
|
def stream_music_mp3():
|
||||||
"""
|
"""
|
||||||
The function `get_music_list` returns a JSON object containing a list of music files.
|
The function `stream_music_mp3` is a route handler for the "/stream_mp3" endpoint.
|
||||||
:return: A JSON response containing a key "music_file_list" with the value of the variable
|
It redirects the user to the URL "http://www.example.com" with a status code of 302.
|
||||||
`music_file_list`.
|
|
||||||
|
:return: A redirect response to "http://www.example.com" with a status code of 302.
|
||||||
"""
|
"""
|
||||||
return redirect("http://www.example.com", code=302)
|
return redirect("http://www.example.com", code=302)
|
||||||
|
|
||||||
@@ -278,19 +316,18 @@ def stream_music_mp3():
|
|||||||
@app.route("/clear_pr_pls", methods=["GET"])
|
@app.route("/clear_pr_pls", methods=["GET"])
|
||||||
def clear_pr_pls():
|
def clear_pr_pls():
|
||||||
"""
|
"""
|
||||||
The function `get_music_list` returns a JSON object containing a list of music files.
|
The function `clear_pr_pls` clears the contents of the priority queue playlist file.
|
||||||
:return: A JSON response containing a key "music_file_list" with the value of the variable
|
|
||||||
`music_file_list`.
|
:return: A JSON response indicating the success of the operation.
|
||||||
"""
|
"""
|
||||||
with open(
|
with open(
|
||||||
"/home/pi/Conjurer/priority_queue.playlist", "w", encoding="utf-8"
|
"/home/pi/Conjurer/priority_queue.playlist", "w", encoding="utf-8"
|
||||||
) as s_file:
|
) as s_file:
|
||||||
s_file.truncate(0)
|
s_file.truncate(0)
|
||||||
return_data = jsonify(isError=False, message="Success", statusCode=200, data=[])
|
return_data = jsonify(isError=False, message="Success", statusCode=200, data=[])
|
||||||
return return_data, 200
|
return return_data, 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/mp3", methods=["GET"])
|
@app.route("/mp3", methods=["GET"])
|
||||||
def get_music_list():
|
def get_music_list():
|
||||||
"""
|
"""
|
||||||
@@ -325,20 +362,22 @@ def update_music_list():
|
|||||||
@app.route("/get_music", methods=["POST"])
|
@app.route("/get_music", methods=["POST"])
|
||||||
def look_for_playlist():
|
def look_for_playlist():
|
||||||
"""
|
"""
|
||||||
The function `update_music_list` receives a POST request with a JSON payload, logs the received
|
The function `look_for_playlist` receives a POST request with a JSON payload, logs the received
|
||||||
item, adds it to a music file list, and returns a success message along with the updated record.
|
item, adds it to a music file list, and returns a success message along with the updated record.
|
||||||
:return: The function `update_music_list` is returning a tuple containing a JSON response and a
|
|
||||||
status code. The JSON response includes keys `isError`, `message`, `statusCode`, and `data`,
|
:return: A tuple containing a JSON response and a status code. The JSON response includes keys `isError`,
|
||||||
with values indicating the success of the operation and the data that was
|
`message`, `statusCode`, and `data`, with values indicating the success of the operation and the
|
||||||
received and added to the `music_file_list`.
|
data that was received and added to the `music_file_list`. The status code returned is 200,
|
||||||
The status code returned is 200, indicating a successful response.
|
indicating a successful response.
|
||||||
"""
|
"""
|
||||||
record = json.loads(request.data)
|
record = json.loads(request.data)
|
||||||
app.logger.info(record)
|
app.logger.info(record)
|
||||||
app.logger.info(record["lista_slow"])
|
app.logger.info(record["lista_slow"])
|
||||||
app.logger.info(record["UUID"])
|
app.logger.info(record["UUID"])
|
||||||
app.logger.info(record["dlugosc_plejlisty"])
|
app.logger.info(record["dlugosc_plejlisty"])
|
||||||
return_data = wyszukaj(record["lista_slow"], record["dlugosc_plejlisty"], app.logger, True)
|
return_data = wyszukaj(
|
||||||
|
record["lista_slow"], record["dlugosc_plejlisty"], app.logger, True
|
||||||
|
)
|
||||||
|
|
||||||
return_data = (
|
return_data = (
|
||||||
jsonify(isError=False, message="Success", statusCode=200, data=return_data),
|
jsonify(isError=False, message="Success", statusCode=200, data=return_data),
|
||||||
@@ -346,23 +385,26 @@ def look_for_playlist():
|
|||||||
)
|
)
|
||||||
return return_data
|
return return_data
|
||||||
|
|
||||||
|
|
||||||
@app.route("/add_to_priority", methods=["POST"])
|
@app.route("/add_to_priority", methods=["POST"])
|
||||||
def add_to_priority():
|
def add_to_priority():
|
||||||
"""
|
"""
|
||||||
The function `update_music_list` receives a POST request with a JSON payload, logs the received
|
The function `add_to_priority` receives a POST request with a JSON payload, logs the received
|
||||||
item, adds it to a music file list, and returns a success message along with the updated record.
|
item, adds it to a music file list, and returns a success message along with the updated record.
|
||||||
:return: The function `update_music_list` is returning a tuple containing a JSON response and a
|
|
||||||
status code. The JSON response includes keys `isError`, `message`, `statusCode`, and `data`,
|
:return: A tuple containing a JSON response and a status code. The JSON response includes keys `isError`,
|
||||||
with values indicating the success of the operation and the data that was
|
`message`, `statusCode`, and `data`, with values indicating the success of the operation and the
|
||||||
received and added to the `music_file_list`.
|
data that was received and added to the `music_file_list`. The status code returned is 200,
|
||||||
The status code returned is 200, indicating a successful response.
|
indicating a successful response.
|
||||||
"""
|
"""
|
||||||
record = json.loads(request.data)
|
record = json.loads(request.data)
|
||||||
app.logger.info(record)
|
app.logger.info(record)
|
||||||
app.logger.info(record["lista_slow"])
|
app.logger.info(record["lista_slow"])
|
||||||
app.logger.info(record["UUID"])
|
app.logger.info(record["UUID"])
|
||||||
app.logger.info(record["dlugosc_plejlisty"])
|
app.logger.info(record["dlugosc_plejlisty"])
|
||||||
return_data = wyszukaj(record["lista_slow"], record["dlugosc_plejlisty"], app.logger, False)
|
return_data = wyszukaj(
|
||||||
|
record["lista_slow"], record["dlugosc_plejlisty"], app.logger, False
|
||||||
|
)
|
||||||
|
|
||||||
return_data = (
|
return_data = (
|
||||||
jsonify(isError=False, message="Success", statusCode=200, data=return_data),
|
jsonify(isError=False, message="Success", statusCode=200, data=return_data),
|
||||||
@@ -382,8 +424,18 @@ def flask_debug():
|
|||||||
|
|
||||||
def waitress_run():
|
def waitress_run():
|
||||||
"""
|
"""
|
||||||
The `waitress_run` function serves the `app` on host "0.0.0.0"
|
The `waitress_run` function serves the `app` on host HOST_ADDRESS
|
||||||
and port 5000 using the Waitress WSGI server.
|
and port 5000 using the Waitress WSGI server.
|
||||||
|
|
||||||
|
This function starts the Waitress server and listens for incoming requests
|
||||||
|
on the specified host and port. It uses the `serve` function from the Waitress
|
||||||
|
library to handle the serving process.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
"""
|
"""
|
||||||
serve(app, host=HOST_ADDRESS, port=PORT_ADDRESS)
|
serve(app, host=HOST_ADDRESS, port=PORT_ADDRESS)
|
||||||
|
|
||||||
@@ -407,7 +459,7 @@ if __name__ == "__main__":
|
|||||||
rescan()
|
rescan()
|
||||||
logger.info("Started")
|
logger.info("Started")
|
||||||
threads = []
|
threads = []
|
||||||
#threads.append(threading.Thread(target=flask_debug))
|
# threads.append(threading.Thread(target=flask_debug))
|
||||||
threads.append(threading.Thread(target=waitress_run))
|
threads.append(threading.Thread(target=waitress_run))
|
||||||
threads.append(threading.Thread(target=thread_rescan))
|
threads.append(threading.Thread(target=thread_rescan))
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +1,71 @@
|
|||||||
|
# FILEPATH: /home/mtuszowski/conjurer/conjurer_musician/radio_conjurer.liq
|
||||||
|
|
||||||
|
# This script sets up a Liquidsoap radio stream with various features and configurations.
|
||||||
|
|
||||||
|
# Load icecast credentials from a JSON file
|
||||||
let json.parse credentials = file.contents("/home/pi/Conjurer/icecast_credentials.json")
|
let json.parse credentials = file.contents("/home/pi/Conjurer/icecast_credentials.json")
|
||||||
|
|
||||||
|
# Enable replaygain metadata processing
|
||||||
enable_replaygain_metadata()
|
enable_replaygain_metadata()
|
||||||
|
|
||||||
|
# Define playlists to be used in the stream
|
||||||
s1 = replaygain(playlist(reload_mode="watch", "/home/pi/Conjurer/all_playlist.playlist"))
|
s1 = replaygain(playlist(reload_mode="watch", "/home/pi/Conjurer/all_playlist.playlist"))
|
||||||
s2 = replaygain(playlist(reload_mode="watch", "/home/pi/Conjurer/priority_queue.playlist"))
|
s2 = replaygain(playlist(reload_mode="watch", "/home/pi/Conjurer/priority_queue.playlist"))
|
||||||
|
s3 = replaygain(playlist(reload_mode="watch", "/home/pi/Conjurer/hit.playlist"))
|
||||||
|
|
||||||
s = random(id="randomizer", weights=[7, 10], [s2, s1])
|
# Randomly select a playlist with weights
|
||||||
|
s = random(id="randomizer", weights=[1, 3, 2], [s1, s2, s3])
|
||||||
|
|
||||||
|
# Set up an interactive harbor for controlling the stream
|
||||||
interactive.harbor(port = 9999)
|
interactive.harbor(port = 9999)
|
||||||
f = interactive.float("f", description="Frequency", min=0., max=1000.,unit="Hz", 200.)
|
|
||||||
g = interactive.float("g", description="Gain", min=0., max=20.,unit="dB", 8.)
|
# Set up interactive controls for bass boost
|
||||||
|
f = interactive.float("f", description="Frequency", min=0., max=1000., unit="Hz", 200.)
|
||||||
|
g = interactive.float("g", description="Gain", min=0., max=20., unit="dB", 8.)
|
||||||
b = bass_boost(frequency=f, gain=g, s)
|
b = bass_boost(frequency=f, gain=g, s)
|
||||||
s = add([s, b])
|
s = add([s, b])
|
||||||
|
|
||||||
|
# Set up interactive control for main volume
|
||||||
a = interactive.float("main_volume", min=0., max=20., 1.)
|
a = interactive.float("main_volume", min=0., max=20., 1.)
|
||||||
s = compress.multiband.interactive(bands=3, s)
|
s = compress.multiband.interactive(bands=3, s)
|
||||||
|
|
||||||
s = nrj(normalize (s))
|
# Apply audio processing effects
|
||||||
|
s = nrj(normalize(s))
|
||||||
s = crossfade(fade_out=3., fade_in=3., duration=5., s)
|
s = crossfade(fade_out=3., fade_in=3., duration=5., s)
|
||||||
s = amplify(a,s)
|
s = amplify(a, s)
|
||||||
|
|
||||||
|
# Skip blank sections in the stream
|
||||||
s = blank.skip(max_blank=2., s)
|
s = blank.skip(max_blank=2., s)
|
||||||
|
|
||||||
|
# Configure logging settings
|
||||||
log_to_stdout = true
|
log_to_stdout = true
|
||||||
log_to_file = true
|
log_to_file = true
|
||||||
logpath = "/home/pi/Conjurer/radio_log.log"
|
logpath = "/home/pi/Conjurer/radio_log.log"
|
||||||
loglevel = 4
|
loglevel = 4
|
||||||
set("log.stdout",log_to_stdout)
|
set("log.stdout", log_to_stdout)
|
||||||
set("log.level",loglevel)
|
set("log.level", loglevel)
|
||||||
|
|
||||||
# Enable logging to file. See variable: logpath
|
# Enable logging to file
|
||||||
set("log.file",log_to_file)
|
set("log.file", log_to_file)
|
||||||
set("log.file.path",logpath)
|
set("log.file.path", logpath)
|
||||||
|
|
||||||
|
# Set up emergency fallback track
|
||||||
emergency = single("/home/pi/RetroPie/mp3/Youtube/Dr. Peacock - Trip to Ireland [GvrvQTUbUcA].mp3")
|
emergency = single("/home/pi/RetroPie/mp3/Youtube/Dr. Peacock - Trip to Ireland [GvrvQTUbUcA].mp3")
|
||||||
radio = fallback(id="switcher",track_sensitive = false, [s, emergency])
|
|
||||||
|
|
||||||
|
# Create a fallback stream with the main stream and emergency track
|
||||||
|
radio = fallback(id="switcher", track_sensitive=false, [s, emergency])
|
||||||
|
|
||||||
|
# Handle metadata events
|
||||||
handle_metadata = fun (m) -> begin
|
handle_metadata = fun (m) -> begin
|
||||||
log.info(m["title"])
|
log.info(m["title"])
|
||||||
log.info(m["artist"])
|
log.info(m["artist"])
|
||||||
log.info(m["filename"])
|
log.info(m["filename"])
|
||||||
end
|
end
|
||||||
radio.on_metadata(handle_metadata)
|
radio.on_metadata(handle_metadata)
|
||||||
|
|
||||||
|
# Enable persistent script parameters
|
||||||
interactive.persistent("script.params")
|
interactive.persistent("script.params")
|
||||||
|
|
||||||
|
# Configure output formats and destinations
|
||||||
# Enable logging on Standard Output and set logging level.
|
output.file.hls("/tmp/hls", [("mp3-low", %mp3(bitrate=96)), ("mp3-hi", %mp3(bitrate=160))], radio)
|
||||||
|
output.icecast(%mp3, host="retropie", port=8000, password=credentials.password, icy_metadata="true", mount="mp3-stream", radio)
|
||||||
output.file.hls("/tmp/hls",[("mp3-low", %mp3(bitrate=96)), ("mp3-hi", %mp3(bitrate=160))],radio)
|
|
||||||
output.icecast(%mp3, host="retropie", port=8000,password=credentials.password, icy_metadata="true", mount="mp3-stream", radio)
|
|
||||||
|
|||||||
@@ -8,9 +8,14 @@ cp /home/pi/conjurer/conjurer_musician/requirements_musician.txt /home/pi/Conjur
|
|||||||
cp /home/pi/conjurer/conjurer_musician/conjurer_musician.py /home/pi/Conjurer/
|
cp /home/pi/conjurer/conjurer_musician/conjurer_musician.py /home/pi/Conjurer/
|
||||||
cp /home/pi/conjurer/conjurer_musician/radio_conjurer.liq /home/pi/Conjurer/
|
cp /home/pi/conjurer/conjurer_musician/radio_conjurer.liq /home/pi/Conjurer/
|
||||||
touch /home/pi/Conjurer/discord_mus_service.log
|
touch /home/pi/Conjurer/discord_mus_service.log
|
||||||
touch /home/pi/Conjurer/all_playlist.txt
|
|
||||||
touch /home/pi/Conjurer/playlist.json
|
touch /home/pi/Conjurer/playlist.json
|
||||||
|
touch /home/pi/Conjurer/prio_playlist.json
|
||||||
|
|
||||||
|
touch /home/pi/Conjurer/all_playlist.playlist
|
||||||
touch /home/pi/Conjurer/priority_queue.playlist
|
touch /home/pi/Conjurer/priority_queue.playlist
|
||||||
|
touch /home/pi/Conjurer/hit.playlist
|
||||||
|
|
||||||
touch /home/pi/Conjurer/radio_log.log
|
touch /home/pi/Conjurer/radio_log.log
|
||||||
./env/bin/python3 -m pip install --upgrade pip
|
./env/bin/python3 -m pip install --upgrade pip
|
||||||
./env/bin/python3 -m pip install -r requirements_musician.txt
|
./env/bin/python3 -m pip install -r requirements_musician.txt
|
||||||
|
|||||||
Reference in New Issue
Block a user