Skip to content

Commit 51665b3

Browse files
committed
adicionando suporte para compilação em Linux, atualizando scripts de build e configurando variáveis de ambiente para o frontend
1 parent 51a2962 commit 51665b3

10 files changed

+257
-35
lines changed

build.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ def build_executable():
2121
if sys.platform == 'win32':
2222
subprocess.run('pyinstaller m3utostrm.spec --clean', shell=True, check=True)
2323
else:
24-
# No Linux, usar o script de build_windows.sh para cross-compilation
25-
subprocess.run('bash build_windows.sh', shell=True, check=True)
24+
subprocess.run('pyinstaller linux.spec --clean', shell=True, check=True)
2625

2726
def main():
2827
# Limpar builds anteriores
@@ -32,11 +31,16 @@ def main():
3231
shutil.rmtree('build')
3332

3433
try:
35-
# Build do frontend
36-
build_frontend()
37-
38-
# Build do executável
39-
build_executable()
34+
if len(sys.argv) > 1 and sys.argv[1] == '--windows':
35+
# Build para Windows usando Wine
36+
subprocess.run('bash build_windows.sh', shell=True, check=True)
37+
elif len(sys.argv) > 1 and sys.argv[1] == '--linux':
38+
# Build para Linux
39+
subprocess.run('bash build_linux.sh', shell=True, check=True)
40+
else:
41+
# Build para o sistema atual
42+
build_frontend()
43+
build_executable()
4044

4145
print("Build completed successfully!")
4246

build_linux.sh

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/bash
2+
3+
# Criar diretório de assets se não existir
4+
mkdir -p src/assets
5+
6+
# Build do frontend Next.js
7+
echo "Building Next.js frontend..."
8+
cd frontend
9+
# Adicionar variável de ambiente para o endereço do servidor
10+
echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > .env.local
11+
npm install
12+
npm run build
13+
14+
# Garantir que todos os arquivos necessários sejam copiados
15+
mkdir -p dist
16+
cp -r .next/static dist/
17+
cp -r .next/standalone/* dist/ 2>/dev/null || true
18+
cp -r public/* dist/ 2>/dev/null || true
19+
cd ..
20+
21+
# Build para Linux
22+
echo "Executando build para Linux..."
23+
python -m pip install -r requirements.txt
24+
python -m PyInstaller linux.spec --clean
25+
26+
echo "Build concluído! Verifique a pasta dist/"

build_windows.sh

+7-3
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ fi
1212
# Build do frontend Next.js
1313
echo "Building Next.js frontend..."
1414
cd frontend
15+
# Adicionar variável de ambiente para o endereço do servidor
16+
echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > .env.local
1517
npm install
1618
npm run build
19+
20+
# Garantir que todos os arquivos necessários sejam copiados
1721
mkdir -p dist
18-
if [ -d ".next/static" ]; then
19-
cp -r .next/static dist/
20-
fi
22+
cp -r .next/static dist/
23+
cp -r .next/standalone/* dist/ 2>/dev/null || true
24+
cp -r public/* dist/ 2>/dev/null || true
2125
cd ..
2226

2327
# Verificar se estamos no Windows

frontend/next.config.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
/** @type {import('next').NextConfig} */
22
const nextConfig = {
3-
output: 'export', // Habilita exportação estática
3+
output: 'export',
44
images: {
55
unoptimized: true
66
},
7-
trailingSlash: true,
8-
distDir: 'dist'
7+
basePath: '',
8+
distDir: 'dist',
9+
assetPrefix: './', // Importante para caminhos relativos
10+
env: {
11+
API_URL: process.env.NODE_ENV === 'development'
12+
? 'http://localhost:8000/api'
13+
: '/api'
14+
}
915
}
1016

1117
module.exports = nextConfig

linux.spec

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
import os
3+
from PyInstaller.utils.hooks import collect_submodules
4+
5+
block_cipher = None
6+
7+
# Atualizar caminhos do frontend
8+
added_files = [
9+
('frontend/dist', 'frontend/dist'), # Next.js files
10+
('config.json', '.'), # Config file
11+
]
12+
13+
# Garantir que todos os arquivos estáticos do Next.js sejam incluídos
14+
datas = added_files + [
15+
('frontend/dist/static', 'frontend/dist/static'), # Next.js static files
16+
('frontend/dist/_next', 'frontend/dist/_next'), # Next.js assets
17+
]
18+
19+
a = Analysis(
20+
['main.py'],
21+
pathex=[],
22+
binaries=[],
23+
datas=datas,
24+
hiddenimports=[
25+
'tkinter',
26+
'requests',
27+
'urllib3',
28+
'json',
29+
're',
30+
'_decimal',
31+
'decimal',
32+
'asyncio',
33+
'pkg_resources.py2_warn',
34+
'uvicorn.logging',
35+
'uvicorn.loops',
36+
'uvicorn.loops.auto',
37+
'uvicorn.protocols',
38+
'uvicorn.protocols.http',
39+
'uvicorn.protocols.http.auto',
40+
'uvicorn.protocols.websockets',
41+
'uvicorn.protocols.websockets.auto',
42+
'uvicorn.lifespan',
43+
'uvicorn.lifespan.on',
44+
'fastapi',
45+
'websockets',
46+
] + collect_submodules('fastapi'),
47+
excludes=[],
48+
win_no_prefer_redirects=False,
49+
win_private_assemblies=False,
50+
cipher=block_cipher,
51+
noarchive=False,
52+
)
53+
54+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
55+
56+
exe = EXE(
57+
pyz,
58+
a.scripts,
59+
a.binaries,
60+
a.zipfiles,
61+
a.datas,
62+
[],
63+
name='M3UtoSTRM',
64+
debug=False,
65+
bootloader_ignore_signals=False,
66+
strip=False,
67+
upx=True,
68+
runtime_tmpdir=None,
69+
console=False,
70+
disable_windowed_traceback=False,
71+
)

m3utostrm.spec

+10-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@ block_cipher = None
99
icon_path = os.path.join('src', 'assets', 'icon.ico')
1010
icon_file = icon_path if os.path.exists(icon_path) else None
1111

12+
# Atualizar caminhos do frontend
1213
added_files = [
13-
('frontend/dist', 'frontend/dist'), # Next.js static files
14-
('config.json', '.'), # Config file
14+
('frontend/dist', 'frontend/dist'), # Next.js files
15+
('config.json', '.'), # Config file
16+
]
17+
18+
# Garantir que todos os arquivos estáticos do Next.js sejam incluídos
19+
datas = added_files + [
20+
('frontend/dist/static', 'frontend/dist/static'), # Next.js static files
21+
('frontend/dist/_next', 'frontend/dist/_next'), # Next.js assets
1522
]
1623

1724
# Define binaries baseado no sistema operacional
@@ -27,7 +34,7 @@ a = Analysis(
2734
['main.py'],
2835
pathex=[],
2936
binaries=added_binaries,
30-
datas=added_files,
37+
datas=datas, # Usar a nova variável datas
3138
hiddenimports=[
3239
'tkinter',
3340
'requests',

main.py

+29-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
import threading
66
import uvicorn
77
import logging
8+
import signal
9+
import sys
10+
11+
def signal_handler(signum, frame):
12+
"""Manipulador de sinais para encerramento limpo"""
13+
sys.exit(0)
814

915
def run_api():
1016
try:
@@ -13,20 +19,31 @@ def run_api():
1319
logging.error(f"Erro ao iniciar API: {str(e)}")
1420

1521
def main():
16-
# Criar a interface principal
17-
app_window = MainWindow()
18-
19-
# Iniciar system tray
20-
tray = SystemTray(app_window)
21-
tray_thread = threading.Thread(target=tray.run, daemon=True)
22-
tray_thread.start()
22+
# Configurar manipulador de sinais
23+
signal.signal(signal.SIGINT, signal_handler)
24+
signal.signal(signal.SIGTERM, signal_handler)
25+
26+
try:
27+
# Criar a interface principal
28+
app_window = MainWindow()
29+
30+
# Iniciar system tray
31+
tray = SystemTray(app_window)
32+
tray_thread = threading.Thread(target=tray.run, daemon=True)
33+
tray_thread.start()
2334

24-
# Iniciar API em uma thread separada
25-
api_thread = threading.Thread(target=run_api, daemon=True)
26-
api_thread.start()
35+
# Iniciar API em uma thread separada
36+
api_thread = threading.Thread(target=run_api, daemon=True)
37+
api_thread.start()
2738

28-
# Iniciar a interface
29-
app_window.run()
39+
# Iniciar a interface
40+
app_window.run()
41+
except KeyboardInterrupt:
42+
logging.info("Encerrando aplicação...")
43+
except Exception as e:
44+
logging.error(f"Erro inesperado: {str(e)}")
45+
finally:
46+
sys.exit(0)
3047

3148
if __name__ == "__main__":
3249
main()

src/api/app.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from fastapi import FastAPI, UploadFile, File, Form, WebSocket
22
from fastapi.middleware.cors import CORSMiddleware
33
from fastapi.staticfiles import StaticFiles
4-
from fastapi.responses import JSONResponse
4+
from fastapi.responses import JSONResponse, FileResponse
5+
import os
6+
import sys
57
from ..services.queue_manager import QueueManager
68
from ..services.video_handler import VideoHandler
79
from ..services.playlist_manager import PlaylistManager
@@ -19,10 +21,21 @@
1921
queue_manager = QueueManager()
2022
connected_clients: Set[WebSocket] = set()
2123

24+
# Determinar o caminho do frontend
25+
if getattr(sys, 'frozen', False):
26+
# Executando como executável
27+
frontend_path = os.path.join(sys._MEIPASS, 'frontend', 'dist')
28+
else:
29+
# Executando como script
30+
frontend_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'dist')
31+
32+
# Montar arquivos estáticos do Next.js
33+
app.mount("/static", StaticFiles(directory=os.path.join(frontend_path, "static")), name="static")
34+
2235
# Configurar CORS permitindo o Next.js
2336
app.add_middleware(
2437
CORSMiddleware,
25-
allow_origins=["http://localhost:3000"], # Next.js dev server
38+
allow_origins=["*"],
2639
allow_credentials=True,
2740
allow_methods=["*"],
2841
allow_headers=["*"],
@@ -241,3 +254,26 @@ async def get_config():
241254
"omdb_api_key": config_manager.get('omdb_api_key'),
242255
"tmdb_api_key": config_manager.get('tmdb_api_key')
243256
}
257+
258+
@app.get("/api/server-url")
259+
async def get_server_url():
260+
"""Retorna a URL do servidor"""
261+
port = os.getenv("PORT", "8000")
262+
return {"url": f"http://localhost:{port}"}
263+
264+
@app.get("/")
265+
async def serve_frontend():
266+
"""Serve o frontend Next.js"""
267+
return FileResponse(os.path.join(frontend_path, 'index.html'))
268+
269+
@app.get("/{path:path}")
270+
async def serve_frontend_paths(path: str):
271+
"""Serve os caminhos do frontend Next.js"""
272+
file_path = os.path.join(frontend_path, path)
273+
274+
if os.path.exists(file_path):
275+
return FileResponse(file_path)
276+
elif os.path.exists(file_path + '.html'):
277+
return FileResponse(file_path + '.html')
278+
else:
279+
return FileResponse(os.path.join(frontend_path, 'index.html'))

src/services/system_tray.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from PIL import Image
33
from .proxy_server import ProxyServer
44
import sys
5+
import logging
56

67
class SystemTray:
78
def __init__(self, main_window=None):
@@ -62,8 +63,18 @@ def stop_proxy(self):
6263
self.proxy_server.stop()
6364

6465
def quit_application(self):
65-
self.stop_proxy()
66-
self.icon.stop()
66+
try:
67+
self.stop_proxy()
68+
if self.icon:
69+
self.icon.stop()
70+
except Exception as e:
71+
logging.error(f"Erro ao encerrar system tray: {str(e)}")
72+
finally:
73+
if self.main_window:
74+
try:
75+
self.main_window.root.quit()
76+
except:
77+
pass
6778

6879
def exit_application(self):
6980
"""Fecha completamente o aplicativo e o proxy"""
@@ -74,5 +85,11 @@ def exit_application(self):
7485
sys.exit(0)
7586

7687
def run(self):
77-
self.start_proxy()
78-
self.icon.run()
88+
try:
89+
self.start_proxy()
90+
self.icon.run()
91+
except KeyboardInterrupt:
92+
self.quit_application()
93+
except Exception as e:
94+
logging.error(f"Erro no system tray: {str(e)}")
95+
self.quit_application()

0 commit comments

Comments
 (0)