mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
try streaming files as chunks
This commit is contained in:
+50
-4
@@ -4,7 +4,7 @@ Contains all the track routes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import Blueprint, send_file, request
|
from flask import Blueprint, send_file, request, Response
|
||||||
from flask_openapi3 import APIBlueprint, Tag
|
from flask_openapi3 import APIBlueprint, Tag
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from app.api.apischemas import TrackHashSchema
|
from app.api.apischemas import TrackHashSchema
|
||||||
@@ -37,7 +37,7 @@ def send_track_file(path: TrackHashSchema, query: SendTrackFileQuery):
|
|||||||
ext = filename.rsplit(".", maxsplit=1)[-1]
|
ext = filename.rsplit(".", maxsplit=1)[-1]
|
||||||
return f"audio/{ext}"
|
return f"audio/{ext}"
|
||||||
|
|
||||||
# If filepath is provide, try to send that
|
# If filepath is provided, try to send that
|
||||||
if filepath is not None:
|
if filepath is not None:
|
||||||
try:
|
try:
|
||||||
track = TrackStore.get_tracks_by_filepaths([filepath])[0]
|
track = TrackStore.get_tracks_by_filepaths([filepath])[0]
|
||||||
@@ -48,7 +48,7 @@ def send_track_file(path: TrackHashSchema, query: SendTrackFileQuery):
|
|||||||
|
|
||||||
if track_exists:
|
if track_exists:
|
||||||
audio_type = get_mime(filepath)
|
audio_type = get_mime(filepath)
|
||||||
return send_file(filepath, mimetype=audio_type)
|
return send_file_as_chunks(track.filepath, audio_type)
|
||||||
|
|
||||||
# Else, find file by trackhash
|
# Else, find file by trackhash
|
||||||
tracks = TrackStore.get_tracks_by_trackhashes([trackhash])
|
tracks = TrackStore.get_tracks_by_trackhashes([trackhash])
|
||||||
@@ -60,13 +60,59 @@ def send_track_file(path: TrackHashSchema, query: SendTrackFileQuery):
|
|||||||
audio_type = get_mime(track.filepath)
|
audio_type = get_mime(track.filepath)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return send_file(track.filepath, mimetype=audio_type)
|
return send_file_as_chunks(track.filepath, audio_type)
|
||||||
except (FileNotFoundError, OSError) as e:
|
except (FileNotFoundError, OSError) as e:
|
||||||
return msg, 404
|
return msg, 404
|
||||||
|
|
||||||
return msg, 404
|
return msg, 404
|
||||||
|
|
||||||
|
|
||||||
|
def send_file_as_chunks(filepath: str, audio_type: str) -> Response:
|
||||||
|
file_size = os.path.getsize(filepath)
|
||||||
|
start = 0
|
||||||
|
end = file_size - 1
|
||||||
|
|
||||||
|
range_header = request.headers.get("Range")
|
||||||
|
if range_header:
|
||||||
|
start, end = parse_range_header(range_header, file_size)
|
||||||
|
|
||||||
|
chunk_size = 1024 * 1024 # 1MB chunk size (adjust as needed)
|
||||||
|
|
||||||
|
def generate_chunks():
|
||||||
|
with open(filepath, "rb") as file:
|
||||||
|
file.seek(start)
|
||||||
|
remaining_bytes = end - start + 1
|
||||||
|
|
||||||
|
while remaining_bytes > 0:
|
||||||
|
chunk = file.read(min(chunk_size, remaining_bytes))
|
||||||
|
yield chunk
|
||||||
|
remaining_bytes -= len(chunk)
|
||||||
|
|
||||||
|
response = Response(
|
||||||
|
generate_chunks(),
|
||||||
|
206, # Partial Content status code
|
||||||
|
mimetype=audio_type,
|
||||||
|
content_type=audio_type,
|
||||||
|
direct_passthrough=True,
|
||||||
|
)
|
||||||
|
response.headers.add("Content-Range", f"bytes {start}-{end}/{file_size}")
|
||||||
|
response.headers.add("Accept-Ranges", "bytes")
|
||||||
|
response.headers.add("Content-Length", str(end - start + 1))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def parse_range_header(range_header: str, file_size: int) -> tuple[int, int]:
|
||||||
|
try:
|
||||||
|
range_start, range_end = range_header.strip().split("=")[1].split("-")
|
||||||
|
start = int(range_start)
|
||||||
|
end = min(int(range_end), file_size - 1)
|
||||||
|
except ValueError:
|
||||||
|
return 0, file_size - 1
|
||||||
|
|
||||||
|
return start, end
|
||||||
|
|
||||||
|
|
||||||
class GetAudioSilenceBody(BaseModel):
|
class GetAudioSilenceBody(BaseModel):
|
||||||
ending_file: str = Field(
|
ending_file: str = Field(
|
||||||
description="The ending file's path",
|
description="The ending file's path",
|
||||||
|
|||||||
+28
-6
@@ -10,7 +10,7 @@ from app.utils.wintools import win_replace_slash
|
|||||||
from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
|
|
||||||
def create_folder(path: str, count=0) -> Folder:
|
def create_folder(path: str, trackcount=0, foldercount=0) -> Folder:
|
||||||
"""
|
"""
|
||||||
Creates a folder object from a path.
|
Creates a folder object from a path.
|
||||||
"""
|
"""
|
||||||
@@ -20,7 +20,8 @@ def create_folder(path: str, count=0) -> Folder:
|
|||||||
name=folder.name,
|
name=folder.name,
|
||||||
path=win_replace_slash(str(folder)),
|
path=win_replace_slash(str(folder)),
|
||||||
is_sym=folder.is_symlink(),
|
is_sym=folder.is_symlink(),
|
||||||
count=count,
|
trackcount=trackcount,
|
||||||
|
foldercount=foldercount,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,15 +30,36 @@ def get_folders(paths: list[str]):
|
|||||||
Filters out folders that don't have any tracks and
|
Filters out folders that don't have any tracks and
|
||||||
returns a list of folder objects.
|
returns a list of folder objects.
|
||||||
"""
|
"""
|
||||||
count_dict = {path: 0 for path in paths}
|
count_dict = {
|
||||||
|
"tracks": {path: 0 for path in paths},
|
||||||
|
# folders are immediate children of the root folder
|
||||||
|
"folders": {path: 0 for path in paths},
|
||||||
|
}
|
||||||
|
|
||||||
for track in TrackStore.tracks:
|
for track in TrackStore.tracks:
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if track.folder.startswith(path):
|
if track.folder.startswith(path):
|
||||||
count_dict[path] += 1
|
count_dict["tracks"][path] += 1
|
||||||
|
# increment folder count by counting the number of slashes
|
||||||
|
|
||||||
folders = [{"path": path, "count": count_dict[path]} for path in paths]
|
print("slash count", track.folder.count("/"), path.count("/"))
|
||||||
return [create_folder(f["path"], f["count"]) for f in folders if f["count"] > 0]
|
if track.folder.count("/") == path.count("/"):
|
||||||
|
count_dict["folders"][path] += 1
|
||||||
|
|
||||||
|
folders = [
|
||||||
|
{
|
||||||
|
"path": path,
|
||||||
|
"trackcount": count_dict["tracks"][path],
|
||||||
|
"foldercount": count_dict["folders"][path],
|
||||||
|
}
|
||||||
|
for path in paths
|
||||||
|
]
|
||||||
|
|
||||||
|
return [
|
||||||
|
create_folder(f["path"], f["trackcount"], f["foldercount"])
|
||||||
|
for f in folders
|
||||||
|
if f["trackcount"] > 0
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class GetFilesAndDirs:
|
class GetFilesAndDirs:
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ class Folder:
|
|||||||
name: str
|
name: str
|
||||||
path: str
|
path: str
|
||||||
is_sym: bool = False
|
is_sym: bool = False
|
||||||
count: int = 0
|
trackcount: int = 0
|
||||||
|
foldercount: int = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user