mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Fix lots of bugs (#354)
This commit is contained in:
commit
66fa07f341
64
.gitattributes
vendored
64
.gitattributes
vendored
@ -1,63 +1 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
*.Designer.cs linguist-generated=true
|
||||
|
3
.github/workflows/docker.yml
vendored
3
.github/workflows/docker.yml
vendored
@ -24,6 +24,9 @@ jobs:
|
||||
- context: ./scanner
|
||||
label: scanner
|
||||
image: zoriya/kyoo_scanner
|
||||
- context: ./autosync
|
||||
label: autosync
|
||||
image: zoriya/kyoo_autosync
|
||||
- context: ./transcoder
|
||||
label: transcoder
|
||||
image: zoriya/kyoo_transcoder
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="8.0.0" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Dapper" Version="2.1.37" />
|
||||
<PackageReference Include="EntityFrameworkCore.Projectables" Version="4.1.4-prebeta" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
|
@ -151,7 +151,7 @@ public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, IN
|
||||
/// <summary>
|
||||
/// The release date of this episode. It can be null if unknown.
|
||||
/// </summary>
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
public DateOnly? ReleaseDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime AddedDate { get; set; }
|
||||
|
@ -20,6 +20,7 @@ using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
@ -47,7 +48,7 @@ public interface IThumbnails
|
||||
public Image? Logo { get; set; }
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(ImageConvertor))]
|
||||
[JsonConverter(typeof(ImageConvertor))]
|
||||
[SqlFirstColumn(nameof(Source))]
|
||||
public class Image
|
||||
{
|
||||
@ -71,32 +72,32 @@ public class Image
|
||||
Blurhash = blurhash ?? "000000";
|
||||
}
|
||||
|
||||
public class ImageConvertor : TypeConverter
|
||||
public class ImageConvertor : JsonConverter<Image>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
return true;
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(
|
||||
ITypeDescriptorContext? context,
|
||||
CultureInfo? culture,
|
||||
object value
|
||||
public override Image? Read(
|
||||
ref Utf8JsonReader reader,
|
||||
Type typeToConvert,
|
||||
JsonSerializerOptions options
|
||||
)
|
||||
{
|
||||
if (value is not string source)
|
||||
return base.ConvertFrom(context, culture, value)!;
|
||||
return new Image(source);
|
||||
if (reader.TokenType == JsonTokenType.String && reader.GetString() is string source)
|
||||
return new Image(source);
|
||||
using JsonDocument document = JsonDocument.ParseValue(ref reader);
|
||||
return document.RootElement.Deserialize<Image>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
|
||||
public override void Write(
|
||||
Utf8JsonWriter writer,
|
||||
Image value,
|
||||
JsonSerializerOptions options
|
||||
)
|
||||
{
|
||||
return false;
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("source", value.Source);
|
||||
writer.WriteString("blurhash", value.Blurhash);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ public class Movie
|
||||
/// <summary>
|
||||
/// The date this movie aired.
|
||||
/// </summary>
|
||||
public DateTime? AirDate { get; set; }
|
||||
public DateOnly? AirDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime AddedDate { get; set; }
|
||||
@ -120,11 +120,11 @@ public class Movie
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("air_date")]
|
||||
public DateTime? StartAir => AirDate;
|
||||
public DateOnly? StartAir => AirDate;
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("air_date")]
|
||||
public DateTime? EndAir => AirDate;
|
||||
public DateOnly? EndAir => AirDate;
|
||||
|
||||
/// <summary>
|
||||
/// A video of a few minutes that tease the content.
|
||||
|
@ -97,7 +97,7 @@ public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
|
||||
/// <summary>
|
||||
/// The starting air date of this season.
|
||||
/// </summary>
|
||||
public DateTime? StartDate { get; set; }
|
||||
public DateOnly? StartDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime AddedDate { get; set; }
|
||||
@ -105,7 +105,7 @@ public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
|
||||
/// <summary>
|
||||
/// The ending date of this season.
|
||||
/// </summary>
|
||||
public DateTime? EndDate { get; set; }
|
||||
public DateOnly? EndDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Poster { get; set; }
|
||||
|
@ -94,13 +94,13 @@ public class Show
|
||||
/// <summary>
|
||||
/// The date this show started airing. It can be null if this is unknown.
|
||||
/// </summary>
|
||||
public DateTime? StartAir { get; set; }
|
||||
public DateOnly? StartAir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this show finished airing.
|
||||
/// It can also be null if this is unknown.
|
||||
/// </summary>
|
||||
public DateTime? EndAir { get; set; }
|
||||
public DateOnly? EndAir { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime AddedDate { get; set; }
|
||||
@ -121,7 +121,7 @@ public class Show
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("start_air")]
|
||||
public DateTime? AirDate => StartAir;
|
||||
public DateOnly? AirDate => StartAir;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
@ -8,7 +8,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCore.Proxy" Version="4.5.0" />
|
||||
<PackageReference Include="Blurhash.SkiaSharp" Version="2.0.0" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Dapper" Version="2.1.37" />
|
||||
<PackageReference Include="InterpolatedSql.Dapper" Version="2.3.0" />
|
||||
<PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
|
@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Dapper" Version="2.1.37" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.Projectables" Version="4.1.4-prebeta" />
|
||||
<PackageReference Include="InterpolatedSql.Dapper" Version="2.3.0" />
|
||||
|
1317
back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.Designer.cs
generated
Normal file
1317
back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UseDateOnly : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder
|
||||
.AlterDatabase()
|
||||
.Annotation(
|
||||
"Npgsql:Enum:genre",
|
||||
"action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western"
|
||||
)
|
||||
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.Annotation("Npgsql:Enum:watch_status", "completed,watching,droped,planned,deleted")
|
||||
.OldAnnotation(
|
||||
"Npgsql:Enum:genre",
|
||||
"action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western"
|
||||
)
|
||||
.OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.OldAnnotation("Npgsql:Enum:watch_status", "completed,watching,droped,planned");
|
||||
|
||||
migrationBuilder.AlterColumn<DateOnly>(
|
||||
name: "start_air",
|
||||
table: "shows",
|
||||
type: "date",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateOnly>(
|
||||
name: "end_air",
|
||||
table: "shows",
|
||||
type: "date",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateOnly>(
|
||||
name: "start_date",
|
||||
table: "seasons",
|
||||
type: "date",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateOnly>(
|
||||
name: "end_date",
|
||||
table: "seasons",
|
||||
type: "date",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateOnly>(
|
||||
name: "air_date",
|
||||
table: "movies",
|
||||
type: "date",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateOnly>(
|
||||
name: "release_date",
|
||||
table: "episodes",
|
||||
type: "date",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_users_username",
|
||||
table: "users",
|
||||
column: "username",
|
||||
unique: true
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(name: "ix_users_username", table: "users");
|
||||
|
||||
migrationBuilder
|
||||
.AlterDatabase()
|
||||
.Annotation(
|
||||
"Npgsql:Enum:genre",
|
||||
"action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western"
|
||||
)
|
||||
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.Annotation("Npgsql:Enum:watch_status", "completed,watching,droped,planned")
|
||||
.OldAnnotation(
|
||||
"Npgsql:Enum:genre",
|
||||
"action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western"
|
||||
)
|
||||
.OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.OldAnnotation(
|
||||
"Npgsql:Enum:watch_status",
|
||||
"completed,watching,droped,planned,deleted"
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_air",
|
||||
table: "shows",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateOnly),
|
||||
oldType: "date",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "end_air",
|
||||
table: "shows",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateOnly),
|
||||
oldType: "date",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "seasons",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateOnly),
|
||||
oldType: "date",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "end_date",
|
||||
table: "seasons",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateOnly),
|
||||
oldType: "date",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "air_date",
|
||||
table: "movies",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateOnly),
|
||||
oldType: "date",
|
||||
oldNullable: true
|
||||
);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "release_date",
|
||||
table: "episodes",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateOnly),
|
||||
oldType: "date",
|
||||
oldNullable: true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,12 +19,12 @@ namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.12")
|
||||
.HasAnnotation("ProductVersion", "8.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "watch_status", new[] { "completed", "watching", "droped", "planned" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "watch_status", new[] { "completed", "watching", "droped", "planned", "deleted" });
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b =>
|
||||
@ -109,8 +109,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<DateTime?>("ReleaseDate")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
b.Property<DateOnly?>("ReleaseDate")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("release_date");
|
||||
|
||||
b.Property<int?>("Runtime")
|
||||
@ -238,8 +238,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnName("added_date")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
|
||||
b.Property<DateTime?>("AirDate")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
b.Property<DateOnly?>("AirDate")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("air_date");
|
||||
|
||||
b.Property<string[]>("Aliases")
|
||||
@ -373,8 +373,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnName("added_date")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
b.Property<DateOnly?>("EndDate")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("end_date");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
@ -404,8 +404,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("slug");
|
||||
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
b.Property<DateOnly?>("StartDate")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("start_date");
|
||||
|
||||
b.HasKey("Id")
|
||||
@ -440,8 +440,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("text[]")
|
||||
.HasColumnName("aliases");
|
||||
|
||||
b.Property<DateTime?>("EndAir")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
b.Property<DateOnly?>("EndAir")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("end_air");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
@ -473,8 +473,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("slug");
|
||||
|
||||
b.Property<DateTime?>("StartAir")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
b.Property<DateOnly?>("StartAir")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("start_air");
|
||||
|
||||
b.Property<Status>("Status")
|
||||
@ -651,6 +651,10 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_users_slug");
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_users_username");
|
||||
|
||||
b.ToTable("users", (string)null);
|
||||
});
|
||||
|
||||
|
@ -55,7 +55,7 @@ import arrayShuffle from "array-shuffle";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { getCurrentAccount, readCookie, updateAccount } from "@kyoo/models/src/account-internal";
|
||||
import { PortalProvider } from "@gorhom/portal";
|
||||
import { ConnectionError, ErrorContext } from "@kyoo/ui";
|
||||
import { ConnectionError } from "@kyoo/ui";
|
||||
|
||||
const font = Poppins({ weight: ["300", "400", "900"], subsets: ["latin"], display: "swap" });
|
||||
|
||||
@ -136,17 +136,7 @@ const WithLayout = ({ Component, ...props }: { Component: ComponentType }) => {
|
||||
const layoutInfo = (Component as QueryPage).getLayout ?? (({ page }) => page);
|
||||
const { Layout, props: layoutProps } =
|
||||
typeof layoutInfo === "function" ? { Layout: layoutInfo, props: {} } : layoutInfo;
|
||||
return (
|
||||
<Layout
|
||||
page={
|
||||
<ErrorContext>
|
||||
<Component {...props} />
|
||||
</ErrorContext>
|
||||
}
|
||||
randomItems={[]}
|
||||
{...layoutProps}
|
||||
/>
|
||||
);
|
||||
return <Layout page={<Component {...props} />} randomItems={[]} {...layoutProps} />;
|
||||
};
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ReactNode, createContext, useContext, useEffect, useMemo, useRef } from "react";
|
||||
import { ReactNode, createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ServerInfoP, User, UserP } from "./resources";
|
||||
import { z } from "zod";
|
||||
import { zdate } from "./utils";
|
||||
@ -69,7 +69,8 @@ export const ConnectionErrorContext = createContext<{
|
||||
error: KyooErrors | null;
|
||||
loading: boolean;
|
||||
retry?: () => void;
|
||||
}>({ error: null, loading: true });
|
||||
setError: (error: KyooErrors) => void;
|
||||
}>({ error: null, loading: true, setError: () => {} });
|
||||
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
export const AccountProvider = ({
|
||||
@ -96,6 +97,7 @@ export const AccountProvider = ({
|
||||
retry: () => {
|
||||
queryClient.resetQueries({ queryKey: ["auth", "me"] });
|
||||
},
|
||||
setError: () => {},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@ -156,15 +158,18 @@ export const AccountProvider = ({
|
||||
}
|
||||
}, [selected, queryClient]);
|
||||
|
||||
const [permissionError, setPermissionError] = useState<KyooErrors | null>(null);
|
||||
|
||||
return (
|
||||
<AccountContext.Provider value={accounts}>
|
||||
<ConnectionErrorContext.Provider
|
||||
value={{
|
||||
error: selected ? initialSsrError.current ?? user.error : null,
|
||||
error: selected ? initialSsrError.current ?? user.error ?? permissionError : null,
|
||||
loading: user.isLoading,
|
||||
retry: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["auth", "me"] });
|
||||
},
|
||||
setError: setPermissionError,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -49,7 +49,7 @@ export const A = ({
|
||||
replace
|
||||
? {
|
||||
nativeBehavior: "stack-replace",
|
||||
isNestedNavigator: false,
|
||||
isNestedNavigator: true,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
@ -105,7 +105,7 @@ export const Link = ({
|
||||
const linkProps = useLink({
|
||||
href: href ?? "#",
|
||||
replace,
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
// @ts-ignore Missing hrefAttrs type definition.
|
||||
linkProps.hrefAttrs = { ...linkProps.hrefAttrs, target };
|
||||
|
@ -335,7 +335,7 @@ export const EpisodeLine = ({
|
||||
{isLoading || <P numberOfLines={descriptionExpanded ? undefined : 3}>{overview}</P>}
|
||||
</Skeleton>
|
||||
<IconButton
|
||||
{...css(["more"])}
|
||||
{...css(["more", Platform.OS !== "web" && { opacity: 1 }])}
|
||||
icon={descriptionExpanded ? ExpandLess : ExpandMore}
|
||||
{...tooltip(t(descriptionExpanded ? "misc.collapse" : "misc.expand"))}
|
||||
onPress={(e) => {
|
||||
|
@ -18,21 +18,49 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ConnectionErrorContext } from "@kyoo/models";
|
||||
import { Button, H1, P, ts } from "@kyoo/primitives";
|
||||
import { ConnectionErrorContext, useAccount } from "@kyoo/models";
|
||||
import { Button, H1, Icon, Link, P, ts } from "@kyoo/primitives";
|
||||
import { useRouter } from "solito/router";
|
||||
import { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { ErrorView } from "./error";
|
||||
import Register from "@material-symbols/svg-400/rounded/app_registration.svg";
|
||||
|
||||
export const ConnectionError = () => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { error, retry } = useContext(ConnectionErrorContext);
|
||||
const account = useAccount();
|
||||
|
||||
if (error && (error.status === 401 || error.status == 403)) {
|
||||
if (!account) {
|
||||
return (
|
||||
<View
|
||||
{...css({ flexGrow: 1, flexShrink: 1, justifyContent: "center", alignItems: "center" })}
|
||||
>
|
||||
<P>{t("errors.needAccount")}</P>
|
||||
<Button
|
||||
as={Link}
|
||||
href={"/register"}
|
||||
text={t("login.register")}
|
||||
licon={<Icon icon={Register} {...css({ marginRight: ts(2) })} />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (account.isVerified) return <ErrorView error={error} noBubble />;
|
||||
return (
|
||||
<View
|
||||
{...css({ flexGrow: 1, flexShrink: 1, justifyContent: "center", alignItems: "center" })}
|
||||
>
|
||||
<P>{t("errors.needVerification")}</P>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View {...css({ padding: ts(2) })}>
|
||||
<H1 {...css({ textAlign: "center" })}>{t("errors.connection")}</H1>
|
||||
|
@ -18,19 +18,11 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { KyooErrors, useAccount } from "@kyoo/models";
|
||||
import { ConnectionErrorContext, KyooErrors } from "@kyoo/models";
|
||||
import { P } from "@kyoo/primitives";
|
||||
import {
|
||||
ReactElement,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useContext, useLayoutEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { PermissionError } from "./unauthorized";
|
||||
|
||||
export const ErrorView = ({
|
||||
error,
|
||||
@ -40,7 +32,7 @@ export const ErrorView = ({
|
||||
noBubble?: boolean;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
const setError = useErrorContext();
|
||||
const { setError } = useContext(ConnectionErrorContext);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// if this is a permission error, make it go up the tree to have a whole page login screen.
|
||||
@ -65,22 +57,3 @@ export const ErrorView = ({
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorCtx = createContext<(val: KyooErrors | null) => void>(null!);
|
||||
|
||||
export const ErrorContext = ({ children }: { children: ReactElement }) => {
|
||||
const [error, setError] = useState<KyooErrors | null>(null);
|
||||
const account = useAccount();
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
}, [account, children]);
|
||||
|
||||
if (error && (error.status === 401 || error.status === 403))
|
||||
return <PermissionError error={error} />;
|
||||
if (error) return <ErrorView error={error} noBubble />;
|
||||
return <ErrorCtx.Provider value={setError}>{children}</ErrorCtx.Provider>;
|
||||
};
|
||||
export const useErrorContext = () => {
|
||||
return useContext(ErrorCtx);
|
||||
};
|
||||
|
@ -18,13 +18,10 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { KyooErrors, useAccount } from "@kyoo/models";
|
||||
import { Button, Icon, Link, P, ts } from "@kyoo/primitives";
|
||||
import { P } from "@kyoo/primitives";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { rem, useYoshiki } from "yoshiki/native";
|
||||
import { ErrorView } from "./error";
|
||||
import Register from "@material-symbols/svg-400/rounded/app_registration.svg";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
|
||||
export const Unauthorized = ({ missing }: { missing: string[] }) => {
|
||||
const { t } = useTranslation();
|
||||
@ -43,31 +40,3 @@ export const Unauthorized = ({ missing }: { missing: string[] }) => {
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const PermissionError = ({ error }: { error: KyooErrors }) => {
|
||||
const { t } = useTranslation();
|
||||
const { css } = useYoshiki();
|
||||
const account = useAccount();
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
<View
|
||||
{...css({ flexGrow: 1, flexShrink: 1, justifyContent: "center", alignItems: "center" })}
|
||||
>
|
||||
<P>{t("errors.needAccount")}</P>
|
||||
<Button
|
||||
as={Link}
|
||||
href={"/register"}
|
||||
text={t("login.register")}
|
||||
licon={<Icon icon={Register} {...css({ marginRight: ts(2) })} />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (account.isVerified) return <ErrorView error={error} noBubble />;
|
||||
return (
|
||||
<View {...css({ flexGrow: 1, flexShrink: 1, justifyContent: "center", alignItems: "center" })}>
|
||||
<P>{t("errors.needVerification")}</P>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -46,7 +46,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
|
||||
useEffect(() => {
|
||||
if (!apiUrl && Platform.OS !== "web")
|
||||
router.replace("/server-url", undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
}, [apiUrl, router]);
|
||||
|
||||
@ -74,7 +74,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
|
||||
setError(error);
|
||||
if (error) return;
|
||||
router.replace("/", undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
}}
|
||||
{...css({
|
||||
|
@ -106,7 +106,7 @@ export const OidcCallbackPage: QueryPage<{
|
||||
|
||||
function onError(error: string) {
|
||||
router.replace({ pathname: "/login", query: { error, apiUrl } }, undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
}
|
||||
async function run() {
|
||||
@ -114,7 +114,7 @@ export const OidcCallbackPage: QueryPage<{
|
||||
if (loginError) onError(loginError);
|
||||
else {
|
||||
router.replace("/", undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
|
||||
useEffect(() => {
|
||||
if (!apiUrl && Platform.OS !== "web")
|
||||
router.replace("/server-url", undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
}, [apiUrl, router]);
|
||||
|
||||
@ -85,7 +85,7 @@ export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
|
||||
setError(error);
|
||||
if (error) return;
|
||||
router.replace("/", undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
}}
|
||||
{...css({
|
||||
|
@ -157,11 +157,11 @@ export const Player = ({
|
||||
if (!data) return;
|
||||
if (data.type === "movie")
|
||||
router.replace(`/movie/${data.slug}`, undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
else
|
||||
router.replace(next ?? `/show/${data.show!.slug}`, undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
|
||||
});
|
||||
}}
|
||||
{...css(StyleSheet.absoluteFillObject)}
|
||||
|
@ -220,7 +220,7 @@ export const Video = memo(function Video({
|
||||
onLoad={(info) => {
|
||||
setDuration(info.duration);
|
||||
}}
|
||||
onPlayPause={setPlay}
|
||||
onPlaybackStateChanged={(state) => setPlay(state.isPlaying)}
|
||||
fonts={fonts}
|
||||
subtitles={subtitles}
|
||||
onMediaUnsupported={() => {
|
||||
|
@ -24,7 +24,6 @@ declare module "react-native-video" {
|
||||
interface ReactVideoProps {
|
||||
fonts?: string[];
|
||||
subtitles?: Subtitle[];
|
||||
onPlayPause: (isPlaying: boolean) => void;
|
||||
onMediaUnsupported?: () => void;
|
||||
}
|
||||
export type VideoProps = Omit<ReactVideoProps, "source"> & {
|
||||
@ -103,13 +102,18 @@ const Video = forwardRef<VideoRef, VideoProps>(function Video(
|
||||
onLoad?.(info);
|
||||
}}
|
||||
onBuffer={onBuffer}
|
||||
onError={onMediaUnsupported}
|
||||
onError={(error) => {
|
||||
console.error(error);
|
||||
if (mode === PlayMode.Direct) onMediaUnsupported?.();
|
||||
else onError?.(error);
|
||||
}}
|
||||
selectedVideoTrack={
|
||||
video === -1
|
||||
? { type: SelectedVideoTrackType.AUDO }
|
||||
: { type: SelectedVideoTrackType.RESOLUTION, value: video }
|
||||
}
|
||||
selectedAudioTrack={{ type: SelectedTrackType.INDEX, value: audio.index }}
|
||||
// when video file is invalid, audio is undefined
|
||||
selectedAudioTrack={{ type: SelectedTrackType.INDEX, value: audio?.index ?? 0 }}
|
||||
textTracks={subtitles?.map((x) => ({
|
||||
type: MimeTypes.get(x.codec) as any,
|
||||
uri: x.link!,
|
||||
|
@ -115,7 +115,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
||||
onProgress,
|
||||
onError,
|
||||
onEnd,
|
||||
onPlayPause,
|
||||
onPlaybackStateChanged,
|
||||
onMediaUnsupported,
|
||||
fonts,
|
||||
},
|
||||
@ -182,7 +182,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
||||
if (!hls) return;
|
||||
const update = () => {
|
||||
if (!hls) return;
|
||||
hls.audioTrack = audio.index;
|
||||
hls.audioTrack = audio?.index ?? 0;
|
||||
};
|
||||
update();
|
||||
hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, update);
|
||||
@ -234,9 +234,8 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
||||
onLoadedMetadata={() => {
|
||||
if (source.startPosition) setProgress(source.startPosition / 1000);
|
||||
}}
|
||||
// BUG: If this is enabled, switching to fullscreen or opening a menu make a play/pause loop until firefox crash.
|
||||
// onPlay={() => onPlayPause?.call(null, true)}
|
||||
// onPause={() => onPlayPause?.call(null, false)}
|
||||
onPlay={() => onPlaybackStateChanged?.({ isPlaying: true })}
|
||||
onPause={() => onPlaybackStateChanged?.({ isPlaying: false })}
|
||||
onEnded={onEnd}
|
||||
{...css({ width: "100%", height: "100%", objectFit: "contain" })}
|
||||
/>
|
||||
|
@ -9,13 +9,12 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/zoriya/go-mediainfo"
|
||||
)
|
||||
|
||||
type MediaInfo struct {
|
||||
// closed if the mediainfo is ready for read. open otherwise
|
||||
ready <-chan struct{}
|
||||
// The sha1 of the video file.
|
||||
Sha string `json:"sha"`
|
||||
/// The internal path of the video file.
|
||||
@ -177,37 +176,38 @@ var SubtitleExtensions = map[string]string{
|
||||
"vtt": "vtt",
|
||||
}
|
||||
|
||||
var infos = NewCMap[string, *MediaInfo]()
|
||||
type MICache struct {
|
||||
info *MediaInfo
|
||||
ready sync.WaitGroup
|
||||
}
|
||||
|
||||
var infos = NewCMap[string, *MICache]()
|
||||
|
||||
func GetInfo(path string, sha string, route string) (*MediaInfo, error) {
|
||||
var err error
|
||||
|
||||
ret, _ := infos.GetOrCreate(sha, func() *MediaInfo {
|
||||
readyChan := make(chan struct{})
|
||||
mi := &MediaInfo{
|
||||
Sha: sha,
|
||||
ready: readyChan,
|
||||
}
|
||||
ret, _ := infos.GetOrCreate(sha, func() *MICache {
|
||||
mi := &MICache{info: &MediaInfo{Sha: sha}}
|
||||
mi.ready.Add(1)
|
||||
go func() {
|
||||
save_path := fmt.Sprintf("%s/%s/info.json", Settings.Metadata, sha)
|
||||
if err := getSavedInfo(save_path, mi); err == nil {
|
||||
if err := getSavedInfo(save_path, mi.info); err == nil {
|
||||
log.Printf("Using mediainfo cache on filesystem for %s", path)
|
||||
close(readyChan)
|
||||
mi.ready.Done()
|
||||
return
|
||||
}
|
||||
|
||||
var val *MediaInfo
|
||||
val, err = getInfo(path, route)
|
||||
*mi = *val
|
||||
mi.ready = readyChan
|
||||
mi.Sha = sha
|
||||
close(readyChan)
|
||||
saveInfo(save_path, mi)
|
||||
*mi.info = *val
|
||||
mi.info.Sha = sha
|
||||
mi.ready.Done()
|
||||
saveInfo(save_path, mi.info)
|
||||
}()
|
||||
return mi
|
||||
})
|
||||
<-ret.ready
|
||||
return ret, err
|
||||
ret.ready.Wait()
|
||||
return ret.info, err
|
||||
}
|
||||
|
||||
func getSavedInfo[T any](save_path string, mi *T) error {
|
||||
|
@ -15,14 +15,17 @@ type Keyframe struct {
|
||||
Keyframes []float64
|
||||
CanTransmux bool
|
||||
IsDone bool
|
||||
mutex sync.RWMutex
|
||||
ready sync.WaitGroup
|
||||
listeners []func(keyframes []float64)
|
||||
info *KeyframeInfo
|
||||
}
|
||||
type KeyframeInfo struct {
|
||||
mutex sync.RWMutex
|
||||
ready sync.WaitGroup
|
||||
listeners []func(keyframes []float64)
|
||||
}
|
||||
|
||||
func (kf *Keyframe) Get(idx int32) float64 {
|
||||
kf.mutex.RLock()
|
||||
defer kf.mutex.RUnlock()
|
||||
kf.info.mutex.RLock()
|
||||
defer kf.info.mutex.RUnlock()
|
||||
return kf.Keyframes[idx]
|
||||
}
|
||||
|
||||
@ -30,8 +33,8 @@ func (kf *Keyframe) Slice(start int32, end int32) []float64 {
|
||||
if end <= start {
|
||||
return []float64{}
|
||||
}
|
||||
kf.mutex.RLock()
|
||||
defer kf.mutex.RUnlock()
|
||||
kf.info.mutex.RLock()
|
||||
defer kf.info.mutex.RUnlock()
|
||||
ref := kf.Keyframes[start:end]
|
||||
ret := make([]float64, end-start)
|
||||
copy(ret, ref)
|
||||
@ -39,24 +42,24 @@ func (kf *Keyframe) Slice(start int32, end int32) []float64 {
|
||||
}
|
||||
|
||||
func (kf *Keyframe) Length() (int32, bool) {
|
||||
kf.mutex.RLock()
|
||||
defer kf.mutex.RUnlock()
|
||||
kf.info.mutex.RLock()
|
||||
defer kf.info.mutex.RUnlock()
|
||||
return int32(len(kf.Keyframes)), kf.IsDone
|
||||
}
|
||||
|
||||
func (kf *Keyframe) add(values []float64) {
|
||||
kf.mutex.Lock()
|
||||
defer kf.mutex.Unlock()
|
||||
kf.info.mutex.Lock()
|
||||
defer kf.info.mutex.Unlock()
|
||||
kf.Keyframes = append(kf.Keyframes, values...)
|
||||
for _, listener := range kf.listeners {
|
||||
for _, listener := range kf.info.listeners {
|
||||
listener(kf.Keyframes)
|
||||
}
|
||||
}
|
||||
|
||||
func (kf *Keyframe) AddListener(callback func(keyframes []float64)) {
|
||||
kf.mutex.Lock()
|
||||
defer kf.mutex.Unlock()
|
||||
kf.listeners = append(kf.listeners, callback)
|
||||
kf.info.mutex.Lock()
|
||||
defer kf.info.mutex.Unlock()
|
||||
kf.info.listeners = append(kf.info.listeners, callback)
|
||||
}
|
||||
|
||||
var keyframes = NewCMap[string, *Keyframe]()
|
||||
@ -66,13 +69,14 @@ func GetKeyframes(sha string, path string) *Keyframe {
|
||||
kf := &Keyframe{
|
||||
Sha: sha,
|
||||
IsDone: false,
|
||||
info: &KeyframeInfo{},
|
||||
}
|
||||
kf.ready.Add(1)
|
||||
kf.info.ready.Add(1)
|
||||
go func() {
|
||||
save_path := fmt.Sprintf("%s/%s/keyframes.json", Settings.Metadata, sha)
|
||||
if err := getSavedInfo(save_path, kf); err == nil {
|
||||
log.Printf("Using keyframes cache on filesystem for %s", path)
|
||||
kf.ready.Done()
|
||||
kf.info.ready.Done()
|
||||
return
|
||||
}
|
||||
|
||||
@ -83,7 +87,7 @@ func GetKeyframes(sha string, path string) *Keyframe {
|
||||
}()
|
||||
return kf
|
||||
})
|
||||
ret.ready.Wait()
|
||||
ret.info.ready.Wait()
|
||||
return ret
|
||||
}
|
||||
|
||||
@ -154,7 +158,7 @@ func getKeyframes(path string, kf *Keyframe) error {
|
||||
if len(ret) == max {
|
||||
kf.add(ret)
|
||||
if done == 0 {
|
||||
kf.ready.Done()
|
||||
kf.info.ready.Done()
|
||||
} else if done >= 500 {
|
||||
max = 500
|
||||
}
|
||||
@ -165,7 +169,7 @@ func getKeyframes(path string, kf *Keyframe) error {
|
||||
}
|
||||
kf.add(ret)
|
||||
if done == 0 {
|
||||
kf.ready.Done()
|
||||
kf.info.ready.Done()
|
||||
}
|
||||
kf.IsDone = true
|
||||
return nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user