Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.

Commit c0e64df

Browse files
committed
Add filebrowser uploader functionality and update Dockerfile with new environment variables
1 parent 48081b0 commit c0e64df

3 files changed

Lines changed: 163 additions & 2 deletions

File tree

dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ ENV POSTGRES_PASSWORD=""
2525
ENV POSTGRES_DB="hbni"
2626
ENV POSTGRES_HOST="172.17.0.1"
2727
ENV POSTGRES_PORT="5434"
28+
ENV FILEBROWSER_URL="http://10.0.1.209:8080"
29+
ENV FILEBROWSER_USERNAME="admin"
30+
ENV FILEBROWSER_PASSWORD=""
31+
ENV FILEBROWSER_UPLOAD_PATH="HBNI-Audio/Recordings"
2832
ENV SMTP_USERNAME="jaredgrozz@gmail.com"
2933
ENV SMTP_PASSWORD=""
3034
ENV EMAIL_FROM="jaredgrozz@gmail.com"

filebrowser_uploader.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import asyncio
2+
import os
3+
from urllib.parse import quote
4+
5+
import aiohttp
6+
import asyncpg
7+
from dotenv import load_dotenv
8+
9+
load_dotenv()
10+
11+
# Database settings
12+
db_settings = {
13+
"host": os.getenv("POSTGRES_HOST"),
14+
"port": int(os.getenv("POSTGRES_PORT", 5434)),
15+
"database": os.getenv("POSTGRES_DB"),
16+
"user": os.getenv("POSTGRES_USER"),
17+
"password": os.getenv("POSTGRES_PASSWORD"),
18+
}
19+
20+
FILEBROWSER_URL = os.getenv("FILEBROWSER_URL")
21+
FILEBROWSER_USERNAME = os.getenv("FILEBROWSER_USERNAME")
22+
FILEBROWSER_PASSWORD = os.getenv("FILEBROWSER_PASSWORD")
23+
FILEBROWSER_UPLOAD_PATH = os.getenv("FILEBROWSER_UPLOAD_PATH")
24+
25+
26+
async def insert_data_to_db(
27+
pool, file_name, download_url, date, description, length, host, share_hash
28+
):
29+
async with pool.acquire() as conn:
30+
query = """
31+
INSERT INTO audioarchives (filename, date, description, download_link, length, host, share_hash, visit_count, latest_visit)
32+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
33+
"""
34+
await conn.execute(
35+
query,
36+
file_name,
37+
date,
38+
description,
39+
download_url,
40+
length,
41+
host,
42+
share_hash,
43+
0,
44+
None,
45+
)
46+
47+
48+
async def get_public_share_url(file_relative_path: str, token: str) -> str:
49+
headers = {"X-Auth": token.strip(), "accept": "*/*"}
50+
async with aiohttp.ClientSession() as session:
51+
async with session.post(
52+
f"{FILEBROWSER_URL}/api/share/{file_relative_path}",
53+
headers=headers,
54+
json={"path": f"/{file_relative_path}"},
55+
) as response:
56+
if response.status != 200:
57+
body = await response.text()
58+
raise Exception(
59+
f"Failed to create share link: {response.status} - {body}"
60+
)
61+
data = await response.json()
62+
return data["hash"]
63+
64+
65+
async def get_filebrowser_token():
66+
async with aiohttp.ClientSession() as session:
67+
async with session.post(
68+
f"{FILEBROWSER_URL}/api/login",
69+
json={"username": FILEBROWSER_USERNAME, "password": FILEBROWSER_PASSWORD},
70+
) as response:
71+
token = await response.text()
72+
return token.strip()
73+
74+
75+
async def delete_existing_file(file_relative_path: str, token: str):
76+
async with aiohttp.ClientSession() as session:
77+
async with session.delete(
78+
f"{FILEBROWSER_URL}/api/resources/{file_relative_path}",
79+
headers={"X-Auth": token},
80+
) as response:
81+
if response.status not in [200, 204, 404]:
82+
body = await response.text()
83+
raise Exception(
84+
f"Failed to delete existing file: {response.status} - {body}"
85+
)
86+
87+
88+
async def upload_to_filebrowser(file_path, file_name) -> str:
89+
token = await get_filebrowser_token()
90+
headers = {"X-Auth": token.strip(), "accept": "*/*"}
91+
file_relative_path = f"{FILEBROWSER_UPLOAD_PATH}/{file_name}"
92+
93+
await delete_existing_file(file_relative_path, token)
94+
95+
async with aiohttp.ClientSession() as session:
96+
with open(file_path, "rb") as f:
97+
form = aiohttp.FormData()
98+
form.add_field("files", f, filename=file_name, content_type="audio/mpeg")
99+
100+
async with session.post(
101+
f"{FILEBROWSER_URL}/api/resources/{FILEBROWSER_UPLOAD_PATH}/{file_name}",
102+
headers=headers,
103+
data=form,
104+
) as response:
105+
if response.status != 200:
106+
body = await response.text()
107+
raise Exception(f"Upload failed: {response.status} - {body}")
108+
109+
share_url = await get_public_share_url(file_relative_path, token)
110+
return share_url
111+
112+
113+
async def upload(
114+
file_name: str,
115+
file_path: str,
116+
host: str,
117+
description: str,
118+
date: str,
119+
length: float,
120+
):
121+
sanitized_file_name = (
122+
file_name.replace("&", "&")
123+
.replace("&Amp;", "&")
124+
.replace("&", "and")
125+
.replace("/", " or ")
126+
)
127+
128+
try:
129+
share_hash = await upload_to_filebrowser(file_path, sanitized_file_name)
130+
except FileNotFoundError:
131+
raise FileNotFoundError(f"File not found: {file_path}")
132+
except PermissionError:
133+
raise PermissionError(f"Permission denied for reading file: {file_path}")
134+
except Exception as e:
135+
raise RuntimeError(f"Unexpected error while uploading file: {str(e)}")
136+
137+
pool = await asyncpg.create_pool(**db_settings)
138+
download_url = f"https://broadcasting.hbni.net/play_recording/{quote(file_name)}"
139+
await insert_data_to_db(
140+
pool, file_name, download_url, date, description, length, host, share_hash
141+
)
142+
await pool.close()
143+
144+
145+
if __name__ == "__main__":
146+
asyncio.run(
147+
upload(
148+
"Glenwayprivate - Unspecified description - February 20 Thursday 2025 08_50 PM - 1h 36m 34s.mp3",
149+
"CURRENTLY_RECORDING/Glenwayprivate - Unspecified description - February 20 Thursday 2025 08_50 PM - 1h 36m 34s.mp3",
150+
"glenway",
151+
"Unspecified description",
152+
"February 20 Thursday 2025 08_50 PM",
153+
96.53333333333334,
154+
),
155+
debug=True,
156+
)

main.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import requests
1616
from dotenv import load_dotenv
1717

18+
import filebrowser_uploader
1819
import firebase_android_notification
1920
import firebase_web_notification
2021
import send_email
@@ -180,7 +181,7 @@ def process_file(self):
180181
def upload_stream(self, file_name: str, file_path: str):
181182
app_log.info(f"Uploading {self.host}: {self.recording_file_name}")
182183
asyncio.run(
183-
synology_uploader.upload(
184+
filebrowser_uploader.upload(
184185
file_name,
185186
file_path,
186187
self.host,
@@ -298,7 +299,7 @@ def run(self):
298299

299300
if (
300301
host not in self.active_streams and "test" not in host.lower() and "test" not in description.lower()
301-
and not is_recording # It is being recorded by HBNI Audio
302+
# and not is_recording # It is being recorded by HBNI Audio
302303
):
303304
stream = Stream(title, icecast_source, host, description, self.remove_stream)
304305
self.active_streams[host] = stream

0 commit comments

Comments
 (0)