Fix warnings from Ruff and ffmpeg

This commit is contained in:
2025-02-10 20:41:14 +01:00
parent 1ad53240a1
commit 870187e1e4
8 changed files with 100 additions and 1191 deletions

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"python.analysis.typeCheckingMode": "standard",
"cSpell.words": [
"dotenv"
]
}

View File

@ -21,7 +21,7 @@ app: FastAPI = FastAPI(
@app.post("/uploadfiles/", description="Where to send a POST request to upload files.") @app.post("/uploadfiles/", description="Where to send a POST request to upload files.")
async def upload_file(file: Annotated[UploadFile, File()]): async def upload_file(file: Annotated[UploadFile, File()]) -> JSONResponse:
"""Page for uploading files. """Page for uploading files.
If it is a video, we need to make an HTML file, and a thumbnail If it is a video, we need to make an HTML file, and a thumbnail
@ -42,9 +42,9 @@ async def upload_file(file: Annotated[UploadFile, File()]):
return JSONResponse(content={"error": "Content type is None"}, status_code=500) return JSONResponse(content={"error": "Content type is None"}, status_code=500)
if file.content_type.startswith("video/"): if file.content_type.startswith("video/"):
html_url: str = await do_things(file) html_url: str = do_things(file)
else: else:
filename: str = await remove_illegal_chars(file.filename) filename: str = remove_illegal_chars(file.filename)
with Path.open(Path(settings.upload_folder, filename), "wb+") as f: with Path.open(Path(settings.upload_folder, filename), "wb+") as f:
f.write(file.file.read()) f.write(file.file.read())
@ -55,7 +55,7 @@ async def upload_file(file: Annotated[UploadFile, File()]):
return JSONResponse(content={"html_url": html_url}) return JSONResponse(content={"html_url": html_url})
async def remove_illegal_chars(file_name: str) -> str: def remove_illegal_chars(file_name: str) -> str:
"""Remove illegal characters from the filename. """Remove illegal characters from the filename.
Args: Args:
@ -113,7 +113,7 @@ index_html: str = """
@app.get("/", response_class=HTMLResponse, include_in_schema=False) @app.get("/", response_class=HTMLResponse, include_in_schema=False)
async def main(request: Request): # noqa: ARG001 async def main(request: Request) -> str: # noqa: ARG001
"""Our index view. """Our index view.
You can upload files here. You can upload files here.

View File

@ -54,14 +54,25 @@ def make_thumbnail(path_video: str, file_filename: str) -> str:
path_video: Path where video file is stored. path_video: Path where video file is stored.
file_filename: File name for URL. file_filename: File name for URL.
Raises:
ffmpeg.Error: If there is an error creating the thumbnail.
Returns: Returns:
Returns thumbnail filename. Returns thumbnail filename.
""" """
( output_path: str = f"{settings.upload_folder}/{file_filename}.jpg"
ffmpeg.input(path_video, ss="1") try:
.output(f"{settings.upload_folder}/{file_filename}.jpg", vframes=1) (
.overwrite_output() ffmpeg.input(path_video, ss="1")
.run() .output(output_path, vframes=1, format="image2", update=1)
) .overwrite_output()
.run()
)
except ffmpeg.Error:
logger.exception("Error creating thumbnail")
raise
logger.info("Thumbnail created: %s", output_path)
# Return URL for thumbnail. # Return URL for thumbnail.
return f"{settings.serve_domain}/{file_filename}.jpg" return f"{settings.serve_domain}/{file_filename}.jpg"

View File

@ -57,7 +57,7 @@ def save_to_disk(file: UploadFile) -> VideoFile:
return VideoFile(filename, str(file_location)) return VideoFile(filename, str(file_location))
async def do_things(file: UploadFile) -> str: def do_things(file: UploadFile) -> str:
"""Save video to disk, generate HTML, thumbnail, and return a .html URL. """Save video to disk, generate HTML, thumbnail, and return a .html URL.
Args: Args:

1141
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +1,86 @@
[project]
name = "discord-embed"
version = "1.0.0"
description = "Make nice embeds for Discord"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi",
"ffmpeg-python",
"discord-webhook",
"python-multipart",
"python-dotenv",
"Jinja2",
"uvicorn[standard]",
]
[dependency-groups]
dev = ["pytest", "httpx"]
[build-system] [build-system]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
requires = [ requires = ["poetry-core>=1"]
"poetry-core>=1",
]
[tool.poetry] [tool.poetry]
name = "discord-embed" name = "discord-embed"
version = "1.0.0" version = "1.0.0"
description = "Make nice embeds for Discord" description = "Make nice embeds for Discord"
authors = [ "Joakim Hellsén <tlovinator@gmail.com>" ] authors = ["Joakim Hellsén <tlovinator@gmail.com>"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.12" python = "^3.13"
fastapi = "^0.115.0" fastapi = "*"
ffmpeg-python = "^0.2.0" ffmpeg-python = "*"
discord-webhook = "^1.3.1" discord-webhook = "*"
python-multipart = "^0.0.20" python-multipart = "*"
python-dotenv = "^1.0.1" python-dotenv = "*"
Jinja2 = "^3.1.4" Jinja2 = "*"
uvicorn = { extras = [ "standard" ], version = "^0.34.0" } uvicorn = {extras = ["standard"], version = "*"}
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
httpx = "^0.28.0" httpx = "*"
pytest = "^8.3.2" pytest = "*"
pre-commit = "^4.0.0"
[tool.ruff] [tool.ruff]
preview = true preview = true
fix = true lint.select = ["ALL"]
unsafe-fixes = true lint.pydocstyle.convention = "google"
lint.select = [ lint.isort.required-imports = ["from __future__ import annotations"]
"ALL", lint.pycodestyle.ignore-overlong-task-comments = true
] line-length = 120
lint.ignore = [ lint.ignore = [
"ANN201", "CPY001", # Checks for the absence of copyright notices within Python files.
"CPY001", "D100", # Checks for undocumented public module definitions.
"D100", "D104", # Checks for undocumented public package definitions.
"D104", "D106", # Checks for undocumented public class definitions, for nested classes.
"RUF029", "ERA001", # Checks for commented-out Python code.
"FIX002", # Checks for "TODO" comments.
# Conflicting lint rules when using Ruff's formatter
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"COM812", # Checks for the absence of trailing commas.
"COM819", # Checks for the presence of prohibited trailing commas.
"D206", # Checks for docstrings that are indented with tabs.
"D300", # Checks for docstrings that use '''triple single quotes''' instead of """triple double quotes""".
"E111", # Checks for indentation with a non-multiple of 4 spaces.
"E114", # Checks for indentation of comments with a non-multiple of 4 spaces.
"E117", # Checks for over-indented code.
"ISC001", # Checks for implicitly concatenated strings on a single line.
"ISC002", # Checks for implicitly concatenated strings that span multiple lines.
"Q000", # Checks for inline strings that use single quotes or double quotes, depending on the value of the lint.flake8-quotes.inline-quotes option.
"Q001", # Checks for multiline strings that use single quotes or double quotes, depending on the value of the lint.flake8-quotes.multiline-quotes setting.
"Q002", # Checks for docstrings that use single quotes or double quotes, depending on the value of the lint.flake8-quotes.docstring-quotes setting.
"Q003", # Checks for strings that include escaped quotes, and suggests changing the quote style to avoid the need to escape them.
"W191", # Checks for indentation that uses tabs.
] ]
lint.per-file-ignores."tests/**/*.py" = [ [tool.ruff.lint.per-file-ignores]
"ARG", # Unused function args -> fixtures nevertheless are functionally relevant... "**/test_*.py" = [
"FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize() "ARG", # Unused function args -> fixtures nevertheless are functionally relevant...
"S101", # asserts allowed in tests... "FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize()
"PLR2004", # Magic value used in comparison, ...
"S101", # asserts allowed in tests...
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
] ]
lint.pydocstyle.convention = "google"

View File

@ -11,8 +11,7 @@ def test_generate_html_for_videos() -> None:
domain: str = os.environ["SERVE_DOMAIN"] domain: str = os.environ["SERVE_DOMAIN"]
# Remove trailing slash from domain # Remove trailing slash from domain
if domain.endswith("/"): domain = domain.removesuffix("/")
domain = domain[:-1]
# Delete the old HTML file if it exists # Delete the old HTML file if it exists
if Path.exists(Path("Uploads/test_video.mp4.html")): if Path.exists(Path("Uploads/test_video.mp4.html")):

View File

@ -1,6 +1,5 @@
from __future__ import annotations from __future__ import annotations
import imghdr
import os import os
from pathlib import Path from pathlib import Path
@ -24,13 +23,11 @@ def test_make_thumbnail() -> None:
domain: str = domain[:-1] domain: str = domain[:-1]
# Remove thumbnail if it exists # Remove thumbnail if it exists
if Path.exists(Path(f"{settings.upload_folder}/test.mp4.jpg")): thumbnail_path = Path(f"{settings.upload_folder}/test.mp4.jpg")
Path.unlink(Path(f"{settings.upload_folder}/test.mp4.jpg")) if thumbnail_path.exists():
thumbnail_path.unlink()
thumbnail: str = make_thumbnail(TEST_FILE, "test.mp4") thumbnail: str = make_thumbnail(TEST_FILE, "test.mp4")
# Check if thumbnail is a jpeg.
assert imghdr.what(f"{settings.upload_folder}/test.mp4.jpg") == "jpeg"
# Check if it returns the correct URL. # Check if it returns the correct URL.
assert thumbnail == f"{domain}/test.mp4.jpg" assert thumbnail == f"{domain}/test.mp4.jpg"