+
import { computed, ref, watch, onMounted, onUnmounted, nextTick } from "vue"
-import type { CastMember, MediaItem, Movie, MovieUi, Series, SeriesResumePoint, Torrent } from "../types"
+import type {
+ CastMember,
+ MediaItem,
+ Movie,
+ MovieUi,
+ Series,
+ SeriesResumePoint,
+ Torrent,
+} from "../types"
import type { EpisodeWatchEntry } from "../api"
import {
getCoverUrl,
@@ -316,7 +328,9 @@ function getReleaseAtRow(row: number): HTMLElement | null {
}
function getLastReleaseRowBefore(castRow: number): number | null {
- const releases = Array.from(document.querySelectorAll('[data-nav-release-item="true"]'))
+ const releases = Array.from(
+ document.querySelectorAll('[data-nav-release-item="true"]'),
+ )
let best: number | null = null
for (const release of releases) {
const row = parseInt(release.getAttribute("data-nav-row") || "", 10)
@@ -343,11 +357,7 @@ function registerMovieOutOfBoundsShortcut() {
return null
}
- if (
- direction === "left" &&
- current.hasAttribute("data-nav-cast-item") &&
- currentCol === 0
- ) {
+ if (direction === "left" && current.hasAttribute("data-nav-cast-item") && currentCol === 0) {
const targetRow = lastReleaseShortcutRow ?? getLastReleaseRowBefore(currentRow)
if (targetRow === null) return null
return getReleaseAtRow(targetRow)
@@ -756,23 +766,8 @@ const castNavRow = computed(() => {
return hasDesktopSimilarShortcut ? 4 + movieVersions.value.length : 2 + movieVersions.value.length
})
-const collectionMovies = computed((): Array<{
- title: string
- localId: string
- coverPath: string | null
- rootId: string | null
- year: string | null
- hyphenLang: string | null
- isCurrent: boolean
-}> => {
- if (props.item.type !== "movies") return []
-
- const movie = props.item.data as Movie
- const collectionName = movie.info?.collection?.trim()
- if (!collectionName) return []
- const normalizedCollectionName = collectionName.toLowerCase()
-
- const matches: Array<{
+const collectionMovies = computed(
+ (): Array<{
title: string
localId: string
coverPath: string | null
@@ -780,60 +775,77 @@ const collectionMovies = computed((): Array<{
year: string | null
hyphenLang: string | null
isCurrent: boolean
- }> = []
+ }> => {
+ if (props.item.type !== "movies") return []
- let hasCurrentInMatches = false
+ const movie = props.item.data as Movie
+ const collectionName = movie.info?.collection?.trim()
+ if (!collectionName) return []
+ const normalizedCollectionName = collectionName.toLowerCase()
- for (const libraryMovie of props.allMovies || []) {
- const otherCollectionName = libraryMovie.info?.collection?.trim().toLowerCase()
- if (otherCollectionName !== normalizedCollectionName) continue
+ const matches: Array<{
+ title: string
+ localId: string
+ coverPath: string | null
+ rootId: string | null
+ year: string | null
+ hyphenLang: string | null
+ isCurrent: boolean
+ }> = []
- const title = libraryMovie.title || libraryMovie.info?.title
- if (!title) continue
+ let hasCurrentInMatches = false
- const isCurrent = libraryMovie.id === props.item.id
- if (isCurrent) hasCurrentInMatches = true
+ for (const libraryMovie of props.allMovies || []) {
+ const otherCollectionName = libraryMovie.info?.collection?.trim().toLowerCase()
+ if (otherCollectionName !== normalizedCollectionName) continue
- matches.push({
- title,
- localId: libraryMovie.id,
- coverPath: libraryMovie.cover_path || null,
- rootId: libraryMovie.root_id || null,
- year: libraryMovie.year
- ? String(libraryMovie.year)
- : libraryMovie.info?.release_date?.slice(0, 4) || null,
- hyphenLang: normalizeHyphenationLang(libraryMovie.info?.original_language),
- isCurrent,
- })
- }
+ const title = libraryMovie.title || libraryMovie.info?.title
+ if (!title) continue
- if (!hasCurrentInMatches) {
- matches.push({
- title: props.item.title || (props.item.data as Movie).info?.title || "Current movie",
- localId: props.item.id,
- coverPath: props.item.cover_path || null,
- rootId: props.item.root_id || null,
- year: props.item.year
- ? String(props.item.year)
- : (props.item.data as Movie).info?.release_date?.slice(0, 4) || null,
- hyphenLang: normalizeHyphenationLang((props.item.data as Movie).info?.original_language),
- isCurrent: true,
- })
- }
+ const isCurrent = libraryMovie.id === props.item.id
+ if (isCurrent) hasCurrentInMatches = true
- return matches
- .sort((a, b) => {
- const yearA = parseInt(a.year || "", 10)
- const yearB = parseInt(b.year || "", 10)
- const hasYearA = Number.isFinite(yearA)
- const hasYearB = Number.isFinite(yearB)
+ matches.push({
+ title,
+ localId: libraryMovie.id,
+ coverPath: libraryMovie.cover_path || null,
+ rootId: libraryMovie.root_id || null,
+ year: libraryMovie.year
+ ? String(libraryMovie.year)
+ : libraryMovie.info?.release_date?.slice(0, 4) || null,
+ hyphenLang: normalizeHyphenationLang(libraryMovie.info?.original_language),
+ isCurrent,
+ })
+ }
- if (hasYearA && hasYearB && yearA !== yearB) return yearA - yearB
- if (hasYearA !== hasYearB) return hasYearA ? -1 : 1
- return a.title.localeCompare(b.title)
- })
- .slice(0, 24)
-})
+ if (!hasCurrentInMatches) {
+ matches.push({
+ title: props.item.title || (props.item.data as Movie).info?.title || "Current movie",
+ localId: props.item.id,
+ coverPath: props.item.cover_path || null,
+ rootId: props.item.root_id || null,
+ year: props.item.year
+ ? String(props.item.year)
+ : (props.item.data as Movie).info?.release_date?.slice(0, 4) || null,
+ hyphenLang: normalizeHyphenationLang((props.item.data as Movie).info?.original_language),
+ isCurrent: true,
+ })
+ }
+
+ return matches
+ .sort((a, b) => {
+ const yearA = parseInt(a.year || "", 10)
+ const yearB = parseInt(b.year || "", 10)
+ const hasYearA = Number.isFinite(yearA)
+ const hasYearB = Number.isFinite(yearB)
+
+ if (hasYearA && hasYearB && yearA !== yearB) return yearA - yearB
+ if (hasYearA !== hasYearB) return hasYearA ? -1 : 1
+ return a.title.localeCompare(b.title)
+ })
+ .slice(0, 24)
+ },
+)
function normalizeHyphenationLang(language: string | null | undefined): string | null {
if (!language) return null
diff --git a/frontend/src/components/ReleaseActionMenu.vue b/frontend/src/components/ReleaseActionMenu.vue
index 90c3dcf..8c9cb6d 100644
--- a/frontend/src/components/ReleaseActionMenu.vue
+++ b/frontend/src/components/ReleaseActionMenu.vue
@@ -84,7 +84,7 @@ const disabled = computed(() => !props.filePath)
function getFocusableElements(): HTMLElement[] {
if (!menuRef.value) return []
return Array.from(
- menuRef.value.querySelectorAll(".version-action-item:not(:disabled)")
+ menuRef.value.querySelectorAll(".version-action-item:not(:disabled)"),
)
}
diff --git a/frontend/src/components/ReleaseVersionCard.vue b/frontend/src/components/ReleaseVersionCard.vue
index 6f160a3..d3c9fb8 100644
--- a/frontend/src/components/ReleaseVersionCard.vue
+++ b/frontend/src/components/ReleaseVersionCard.vue
@@ -336,7 +336,12 @@ const hdr10PlusPattern = /hdr10\+|hdr10plus/i
const hasHdr10Plus = computed(() => {
if (props.torrent.hdr10plus) return true
- const text = [props.torrent.title, props.torrent.quality, props.torrent.codec, props.torrent.audio]
+ const text = [
+ props.torrent.title,
+ props.torrent.quality,
+ props.torrent.codec,
+ props.torrent.audio,
+ ]
.filter(Boolean)
.join(" ")
return hdr10PlusPattern.test(text)
diff --git a/frontend/src/components/SeriesFullView.vue b/frontend/src/components/SeriesFullView.vue
index 39ae1df..1fbba73 100644
--- a/frontend/src/components/SeriesFullView.vue
+++ b/frontend/src/components/SeriesFullView.vue
@@ -96,7 +96,9 @@
formatDate(selectedSeason.air_date)
}}
{{ selectedSeason.episode_count ?? selectedSeason.episodes.length }}
+ >{{
+ selectedSeason.episode_count ?? selectedSeason.episodes.length
+ }}
Episodes
@@ -161,9 +163,7 @@
{{ episodeWatchIndicator(episode) }}
▶
@@ -367,9 +367,7 @@ const cursorGlobalIndex = computed(() =>
const resumePointGlobalIndex = computed(() => {
const point = props.resumePoint
if (!point) return null
- const seasonIndex = props.series.seasons.findIndex(
- (s) => s.season_number === point.seasonNumber,
- )
+ const seasonIndex = props.series.seasons.findIndex((s) => s.season_number === point.seasonNumber)
if (seasonIndex < 0) return null
const episodeIndex = props.series.seasons[seasonIndex]?.episodes.findIndex(
(e) => e.episode_number === point.episodeNumber,
@@ -650,10 +648,7 @@ watch(
episodeFocusTarget,
(ep) => {
if (!ep) return
- if (
- !props.focusEpisode &&
- (seasonUserInteracted.value || episodeCursorIndex.value !== null)
- ) {
+ if (!props.focusEpisode && (seasonUserInteracted.value || episodeCursorIndex.value !== null)) {
return
}
const seasonIndex =
@@ -1956,7 +1951,12 @@ html:not(.mouse-active) .episode-tile.nav-focused .tile-focus-outline rect {
rgba(255, 255, 255, 0.025) 30%,
rgba(255, 255, 255, 0) 55%
),
- linear-gradient(to bottom, rgba(20, 20, 28, 0.5) 0%, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.45) 100%);
+ linear-gradient(
+ to bottom,
+ rgba(20, 20, 28, 0.5) 0%,
+ rgba(0, 0, 0, 0) 40%,
+ rgba(0, 0, 0, 0.45) 100%
+ );
}
.episode-tile--ahead::after {
diff --git a/frontend/src/composables/useGamepadNavigation.ts b/frontend/src/composables/useGamepadNavigation.ts
index 59be311..1f603ae 100644
--- a/frontend/src/composables/useGamepadNavigation.ts
+++ b/frontend/src/composables/useGamepadNavigation.ts
@@ -110,7 +110,9 @@ function getDigitalRepeatIntervalMs(holdMs: number): number {
function getAnalogRepeatIntervalMs(intensity: number): number {
const normalized = Math.min(Math.max(intensity, 0), 1)
- return Math.round(ANALOG_REPEAT_MAX_MS - (ANALOG_REPEAT_MAX_MS - ANALOG_REPEAT_MIN_MS) * normalized)
+ return Math.round(
+ ANALOG_REPEAT_MAX_MS - (ANALOG_REPEAT_MAX_MS - ANALOG_REPEAT_MIN_MS) * normalized,
+ )
}
function normalizeAxisIntensity(rawValue: number): number {
diff --git a/frontend/src/composables/useInputModality.ts b/frontend/src/composables/useInputModality.ts
index f022e3a..ddae097 100644
--- a/frontend/src/composables/useInputModality.ts
+++ b/frontend/src/composables/useInputModality.ts
@@ -2,7 +2,6 @@ import { reportUserActivity } from "../api"
type InputModality = "mouse" | "keyboard" | "gamepad"
-
const MOUSE_IDLE_MS = 1400
const MOUSE_INTENT_DISTANCE_PX = 28
const MOUSE_INTENT_WINDOW_MS = 700
diff --git a/frontend/src/composables/useKeyboardNavigation.ts b/frontend/src/composables/useKeyboardNavigation.ts
index 35c1be6..1f69a39 100644
--- a/frontend/src/composables/useKeyboardNavigation.ts
+++ b/frontend/src/composables/useKeyboardNavigation.ts
@@ -103,7 +103,10 @@ function measureGlobalMetrics(group: string): boolean {
const rowStyle = window.getComputedStyle(row)
const paddingLeft = parseFloat(rowStyle.paddingLeft || "0")
const viewportWidth = row.clientWidth
- const deadzoneInset = Math.max(paddingLeft, (viewportWidth - cardWidth) * SYNC_SCROLL_DEADZONE_RATIO)
+ const deadzoneInset = Math.max(
+ paddingLeft,
+ (viewportWidth - cardWidth) * SYNC_SCROLL_DEADZONE_RATIO,
+ )
const leftDeadzoneRaw = rowStyle.getPropertyValue(SYNC_SCROLL_LEFT_DEADZONE_VAR).trim()
const leftDeadzone = Number.isFinite(parseFloat(leftDeadzoneRaw))
? Math.max(0, parseFloat(leftDeadzoneRaw))
@@ -175,10 +178,7 @@ function getRowMaxScroll(row: HTMLElement): number {
if (n === 0) return 0
const lastCol = n - 1
const lastItemLeft = m.paddingLeft + lastCol * m.stride
- const maxVisibleLeft = Math.max(
- m.paddingLeft,
- m.viewportWidth - m.cardWidth - m.rightDeadzone,
- )
+ const maxVisibleLeft = Math.max(m.paddingLeft, m.viewportWidth - m.cardWidth - m.rightDeadzone)
return Math.max(0, lastItemLeft - maxVisibleLeft)
}
@@ -280,14 +280,8 @@ function updateSyncedRowTarget(anchorCol: number, anchorRow: HTMLElement | null
if (!m) return
const itemLeft = m.paddingLeft + anchorCol * m.stride
- const leftVisibleLimit = Math.max(
- m.paddingLeft,
- m.leftDeadzone,
- )
- const rightVisibleLimit = Math.max(
- m.paddingLeft,
- m.viewportWidth - m.cardWidth - m.rightDeadzone,
- )
+ const leftVisibleLimit = Math.max(m.paddingLeft, m.leftDeadzone)
+ const rightVisibleLimit = Math.max(m.paddingLeft, m.viewportWidth - m.cardWidth - m.rightDeadzone)
// Keep focus inside the deadzone: no scroll while the focused item remains
// between left and right limits.
@@ -354,7 +348,9 @@ function getLocalSyncedRowCol(
element: HTMLElement,
requestedCol: number,
): number {
- const cards = Array.from(anchorRow.querySelectorAll
(`.media-card[${FOCUSABLE_ATTR}]`))
+ const cards = Array.from(
+ anchorRow.querySelectorAll(`.media-card[${FOCUSABLE_ATTR}]`),
+ )
if (cards.length === 0) return Math.max(0, requestedCol)
const cardCols = cards
@@ -466,9 +462,7 @@ function ensureElementVisibleVertically(element: HTMLElement) {
// Element finding / navigation (unchanged logic, uses getMetrics() now)
// ---------------------------------------------------------------------------
-function resolveOutOfBoundsNavigation(
- context: OutOfBoundsNavigationContext,
-): HTMLElement | null {
+function resolveOutOfBoundsNavigation(context: OutOfBoundsNavigationContext): HTMLElement | null {
const handlers = Array.from(outOfBoundsHandlers)
for (let i = handlers.length - 1; i >= 0; i--) {
const result = handlers[i]?.(context)
@@ -623,10 +617,7 @@ function findElementClosestToLogicalViewportX(
return nearest
}
-function findNextElement(
- current: HTMLElement,
- direction: NavDirection,
-): HTMLElement | null {
+function findNextElement(current: HTMLElement, direction: NavDirection): HTMLElement | null {
const currentRow = parseInt(current.getAttribute(ROW_ATTR) || "0", 10)
const currentCol = parseInt(current.getAttribute(COL_ATTR) || "0", 10)
const byRow = getElementsByRow()
@@ -682,9 +673,7 @@ function findNextElement(
for (const el of targetRowElements) {
const fromAboveCol = el.element.getAttribute(ENTRY_COL_FROM_ABOVE_ATTR)
if (fromAboveCol === null) continue
- const fromAboveTarget = targetRowElements.find(
- (e) => e.col === parseInt(fromAboveCol, 10),
- )
+ const fromAboveTarget = targetRowElements.find((e) => e.col === parseInt(fromAboveCol, 10))
if (fromAboveTarget) {
desiredCol.value = fromAboveTarget.col
return fromAboveTarget.element
diff --git a/frontend/src/composables/useMediaWebSocket.ts b/frontend/src/composables/useMediaWebSocket.ts
index 413b3ee..9c6c6a6 100644
--- a/frontend/src/composables/useMediaWebSocket.ts
+++ b/frontend/src/composables/useMediaWebSocket.ts
@@ -159,10 +159,7 @@ export function useMediaWebSocket() {
return { ...normalizeSeries(series, rootId, people), id, root_id: rootId }
}
- function normalizeCastMember(
- member: unknown,
- people: Map,
- ): CastMember {
+ function normalizeCastMember(member: unknown, people: Map): CastMember {
if (!Array.isArray(member)) {
return {
name: "",
@@ -346,7 +343,10 @@ export function useMediaWebSocket() {
}
}
- function mergeItemsByHash(items: T[], mergeFn: (a: T, b: T) => T): T[] {
+ function mergeItemsByHash(
+ items: T[],
+ mergeFn: (a: T, b: T) => T,
+ ): T[] {
const map = new Map()
for (const item of items) {
const hash = getContentHash(item.id)
@@ -436,7 +436,14 @@ export function useMediaWebSocket() {
connected.value = wsRef.value?.readyState === WebSocket.OPEN
}
- function applyRootInit(rootId: string, rootData: { movies: Record; series: Record; people?: Record }) {
+ function applyRootInit(
+ rootId: string,
+ rootData: {
+ movies: Record
+ series: Record
+ people?: Record
+ },
+ ) {
const state = ensureRootState(rootId)
state.peopleMap.clear()
@@ -502,7 +509,10 @@ export function useMediaWebSocket() {
const parsed = Number(id)
const normalized = normalizePerson(person)
if (Number.isFinite(parsed)) {
- state.peopleMap.set(parsed, normalized || { name: "", profile_path: null, gender: null })
+ state.peopleMap.set(
+ parsed,
+ normalized || { name: "", profile_path: null, gender: null },
+ )
}
}
}
diff --git a/frontend/src/composables/useSettings.ts b/frontend/src/composables/useSettings.ts
index 3844898..8b1c6c4 100644
--- a/frontend/src/composables/useSettings.ts
+++ b/frontend/src/composables/useSettings.ts
@@ -17,9 +17,9 @@ const STORAGE_KEY = "MediaHive"
const RESOLUTION_PRIORITY: Record = {
"8K": 5,
"4K": 4,
- "FHD": 3,
- "HD": 2,
- "SD": 1,
+ FHD: 3,
+ HD: 2,
+ SD: 1,
}
// Max resolution priority allowed for each preference level
@@ -59,7 +59,13 @@ function loadSettings(): MediaHiveSettings {
} catch {
// ignore parse errors
}
- return { preferredResolution: "rmax", preferredHdr: "none", playerId: "default", playerCustomCmd: null, playerMpcPort: null }
+ return {
+ preferredResolution: "rmax",
+ preferredHdr: "none",
+ playerId: "default",
+ playerCustomCmd: null,
+ playerMpcPort: null,
+ }
}
const settings = reactive(loadSettings())
diff --git a/frontend/src/search-worker.ts b/frontend/src/search-worker.ts
index f9de951..56148f2 100644
--- a/frontend/src/search-worker.ts
+++ b/frontend/src/search-worker.ts
@@ -1,13 +1,7 @@
// Search Web Worker - runs search off the main thread
// This file is loaded as a Web Worker, not imported as a module.
-import type {
- MovieUi,
- SeriesUi,
- MatchedPerson,
- MatchedEpisode,
- SearchMatchInfo,
-} from "./types"
+import type { MovieUi, SeriesUi, MatchedPerson, MatchedEpisode, SearchMatchInfo } from "./types"
// ---------------------------------------------------------------------------
// Message types
@@ -65,7 +59,7 @@ let series: SeriesUi[] = []
function normalizeSearchText(value: string): string {
return value
.toLowerCase()
- .replace(/[^a-z0-9\-]+/g, " ")
+ .replace(/[^a-z0-9-]+/g, " ")
.trim()
.replace(/\s+/g, " ")
}
@@ -277,7 +271,9 @@ function getMatchedWordIndexes(queryWords: string[], value: string): number[] {
}
function mergePersonMatch(target: PersonMatch[], candidate: PersonCandidate): void {
- const existing = target.find((person) => person.name.toLowerCase() === candidate.name.toLowerCase())
+ const existing = target.find(
+ (person) => person.name.toLowerCase() === candidate.name.toLowerCase(),
+ )
if (existing) {
if (!existing.roles.includes(candidate.role)) existing.roles.push(candidate.role)
if (candidate.highlightRoles) existing.highlightRoles = true
@@ -292,9 +288,7 @@ function mergePersonMatch(target: PersonMatch[], candidate: PersonCandidate): vo
}
function getBestContiguousWordRun(indexes: number[], availableIndexes: Set): number[] {
- const sorted = indexes
- .filter((index) => availableIndexes.has(index))
- .sort((a, b) => a - b)
+ const sorted = indexes.filter((index) => availableIndexes.has(index)).sort((a, b) => a - b)
if (sorted.length === 0) return []
diff --git a/frontend/src/utils/languageFlags.ts b/frontend/src/utils/languageFlags.ts
index 29dda12..b17337f 100644
--- a/frontend/src/utils/languageFlags.ts
+++ b/frontend/src/utils/languageFlags.ts
@@ -566,9 +566,7 @@ export function formatLanguageFlagTitle(
): string {
const names: string[] = []
const variants: string[] = []
- const external = new Set(
- (externalCodes ?? []).map((c) => resolveLanguageIdentifier(c)),
- )
+ const external = new Set((externalCodes ?? []).map((c) => resolveLanguageIdentifier(c)))
let hasExternal = false
for (const code of entry.sourceCodes) {
const normalized = resolveLanguageIdentifier(code)
@@ -578,9 +576,10 @@ export function formatLanguageFlagTitle(
// Explicit region tags (en-us, es-419) become parenthesized variants;
// plain codes contribute their host country.
const suffix = normalized.split("-").pop() ?? ""
- const region = /^[a-z]{2}$|^\d{3}$/i.test(suffix) && normalized.includes("-")
- ? suffix.toUpperCase()
- : mapLanguageToCountry(code)
+ const region =
+ /^[a-z]{2}$|^\d{3}$/i.test(suffix) && normalized.includes("-")
+ ? suffix.toUpperCase()
+ : mapLanguageToCountry(code)
const regionName = region ? toRegionName(region) : null
const variant = external.has(normalized)
? regionName
diff --git a/mediahive/hivescan/scanning.py b/mediahive/hivescan/scanning.py
index 0d94478..4ef2655 100644
--- a/mediahive/hivescan/scanning.py
+++ b/mediahive/hivescan/scanning.py
@@ -37,12 +37,33 @@ _SUBTITLE_FLAG_TOKENS = {"forced", "sdh", "cc", "hi", "dhi", "commentary", "sign
# ISO 639-1 -> ISO 639-2/B for common sidecar language tags, so they merge
# with the codes ffmpeg reports for embedded tracks.
_ISO_639_1_TO_639_2 = {
- "ar": "ara", "cs": "ces", "da": "dan", "de": "deu", "el": "ell",
- "en": "eng", "es": "esp", "fi": "fin", "fr": "fra", "he": "heb",
- "hi": "hin", "hu": "hun", "id": "ind", "it": "ita", "ja": "jpn",
- "ko": "kor", "nl": "nld", "no": "nor", "pl": "pol", "pt": "por",
- "ru": "rus", "sv": "swe", "th": "tha", "tr": "tur", "uk": "ukr",
- "vi": "vie", "zh": "zho",
+ "ar": "ara",
+ "cs": "ces",
+ "da": "dan",
+ "de": "deu",
+ "el": "ell",
+ "en": "eng",
+ "es": "esp",
+ "fi": "fin",
+ "fr": "fra",
+ "he": "heb",
+ "hi": "hin",
+ "hu": "hun",
+ "id": "ind",
+ "it": "ita",
+ "ja": "jpn",
+ "ko": "kor",
+ "nl": "nld",
+ "no": "nor",
+ "pl": "pol",
+ "pt": "por",
+ "ru": "rus",
+ "sv": "swe",
+ "th": "tha",
+ "tr": "tur",
+ "uk": "ukr",
+ "vi": "vie",
+ "zh": "zho",
}
# Caches for expensive operations. These are per-scan only: the scanner
diff --git a/pyproject.toml b/pyproject.toml
index af08f94..f4baccd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -73,52 +73,52 @@ preview = true
[tool.ruff.lint]
select = ["ALL"]
ignore = [
- "D203",
- "D213",
- "DOC201",
- "COM812",
- "T201",
+ "incorrect-blank-line-before-class",
+ "multi-line-summary-second-line",
+ "docstring-missing-returns",
+ "missing-trailing-comma",
+ "print",
"EM",
"TC",
- "TRY003",
+ "raise-vanilla-args",
"S",
- "CPY001",
+ "missing-copyright-notice",
"PLR",
"PLW",
# TEMP suppressions - revisit and remove after focused cleanup passes.
- "C901",
- "DOC501",
- "ANN201",
- "D103",
- "FBT001",
- "ANN001",
- "D102",
- "TRY300",
- "D107",
- "ANN202",
- "B904",
- "ASYNC220",
- "E501",
- "INP001",
- "FBT002",
- "PLC0415",
- "D101",
- "RUF006",
- "SLF001",
- "ASYNC240",
- "N801",
- "SIM102",
- "DTZ005",
- "RUF034",
- "D415",
- "D400",
- "ANN401",
+ "complex-structure",
+ "docstring-missing-exception",
+ "missing-return-type-undocumented-public-function",
+ "undocumented-public-function",
+ "boolean-type-hint-positional-argument",
+ "missing-type-function-argument",
+ "undocumented-public-method",
+ "try-consider-else",
+ "undocumented-public-init",
+ "missing-return-type-private-function",
+ "raise-without-from-inside-except",
+ "create-subprocess-in-async-function",
+ "line-too-long",
+ "implicit-namespace-package",
+ "boolean-default-value-positional-argument",
+ "import-outside-top-level",
+ "undocumented-public-class",
+ "asyncio-dangling-task",
+ "private-member-access",
+ "blocking-path-method-in-async-function",
+ "invalid-class-name",
+ "collapsible-if",
+ "call-datetime-now-without-tzinfo",
+ "useless-if-else",
+ "missing-terminal-punctuation",
+ "missing-trailing-period",
+ "any-type",
# Allow en-dash in docstrings (used for list formatting)
- "RUF002",
+ "ambiguous-unicode-character-docstring",
# Allow ctypes COM variable names (CLSID_*, IID_*, etc.)
- "N806",
+ "non-lowercase-variable-in-function",
# Allow inline comments that describe output formats
- "ERA001",
+ "commented-out-code",
# Allow unused local variables in ctypes COM boilerplate
- "F841",
+ "unused-variable",
]
diff --git a/scripts/guibuild.py b/scripts/guibuild.py
index 9166d87..b4587ca 100755
--- a/scripts/guibuild.py
+++ b/scripts/guibuild.py
@@ -82,10 +82,34 @@ class _Platform(NamedTuple):
def _platform() -> _Platform:
if sys.platform == "win32":
- return _Platform("win64", "win", "win-x64", "MediaHive", "mediahive.ico", "MediaHive.exe", ".exe")
+ return _Platform(
+ "win64",
+ "win",
+ "win-x64",
+ "MediaHive",
+ "mediahive.ico",
+ "MediaHive.exe",
+ ".exe",
+ )
if sys.platform == "darwin":
- return _Platform("macos", "osx", "osx-arm64", "MediaHive.app", "mediahive.icns", "MediaHive", ".pkg")
- return _Platform("linux", "linux", "linux-x64", "MediaHive", "mediahive.png", "MediaHive", ".AppImage")
+ return _Platform(
+ "macos",
+ "osx",
+ "osx-arm64",
+ "MediaHive.app",
+ "mediahive.icns",
+ "MediaHive",
+ ".pkg",
+ )
+ return _Platform(
+ "linux",
+ "linux",
+ "linux-x64",
+ "MediaHive",
+ "mediahive.png",
+ "MediaHive",
+ ".AppImage",
+ )
def setup_artifact_name() -> str:
@@ -253,7 +277,7 @@ def _dotnet_runtime_major(exe: Path) -> int | None:
result = subprocess.run(
[str(exe), "--list-runtimes"], capture_output=True, text=True, timeout=30
)
- except (OSError, subprocess.TimeoutExpired):
+ except OSError, subprocess.TimeoutExpired:
return None
if result.returncode != 0:
return None
@@ -455,20 +479,26 @@ def force_macos_user_install(pkg: Path) -> None:
components = list(expanded.glob("*.pkg"))
if len(components) != 1:
contents = sorted(p.name for p in expanded.iterdir())
- raise RuntimeError(f"Unexpected pkg layout: components={components} in {contents}")
+ raise RuntimeError(
+ f"Unexpected pkg layout: components={components} in {contents}"
+ )
component = components[0]
if component.is_dir():
comp_dir = component
else:
comp_dir = expanded / (component.stem + "-component")
- subprocess.run(["pkgutil", "--expand", str(component), str(comp_dir)], check=True)
+ subprocess.run(
+ ["pkgutil", "--expand", str(component), str(comp_dir)], check=True
+ )
postinstall = comp_dir / "Scripts" / "postinstall"
script = postinstall.read_text()
if 'sudo -u "$USER" ' not in script:
raise RuntimeError("Unexpected postinstall script: sudo prefix not found")
postinstall.write_text(script.replace('sudo -u "$USER" ', ""))
if comp_dir is not component:
- subprocess.run(["pkgutil", "--flatten", str(comp_dir), str(component)], check=True)
+ subprocess.run(
+ ["pkgutil", "--flatten", str(comp_dir), str(component)], check=True
+ )
shutil.rmtree(comp_dir)
subprocess.run(["pkgutil", "--flatten", str(expanded), str(pkg)], check=True)
diff --git a/scripts/release.py b/scripts/release.py
index be3d38f..7de82d0 100755
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -73,7 +73,9 @@ def load_token() -> str:
# MediaHive-macos-setup.pkg, MediaHive-linux-setup.AppImage,
# MediaHive-win64-portable.zip) so /releases/download/latest/ links
# stay valid. The version comes from setuptools_scm instead.
-_ARTIFACT_RE = re.compile(r"^MediaHive-(?!\d)[A-Za-z0-9._-]+\.(?:zip|dmg|exe|pkg|AppImage)$")
+_ARTIFACT_RE = re.compile(
+ r"^MediaHive-(?!\d)[A-Za-z0-9._-]+\.(?:zip|dmg|exe|pkg|AppImage)$"
+)
def read_version() -> str:
@@ -89,7 +91,9 @@ def read_version() -> str:
def find_releasable_artifacts() -> list[Path]:
"""Return platform artifact paths in build/."""
build_dir = REPO_ROOT / "build"
- return [p for p in sorted(build_dir.glob("MediaHive-*")) if _ARTIFACT_RE.match(p.name)]
+ return [
+ p for p in sorted(build_dir.glob("MediaHive-*")) if _ARTIFACT_RE.match(p.name)
+ ]
def find_dist_files(version: str) -> list[Path]: