mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-08-30 23:00:06 -04:00
Estimated time is coded up.
This commit is contained in:
parent
ab6669703d
commit
64ee5ee459
@ -10,4 +10,8 @@ namespace API.Controllers;
|
||||
[Authorize]
|
||||
public class BaseApiController : ControllerBase
|
||||
{
|
||||
public BaseApiController()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -41,13 +41,12 @@ public class BookController : BaseApiController
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{chapterId}/book-info")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["chapterId", "includeWordCounts"])]
|
||||
public async Task<ActionResult<BookInfoDto>> GetBookInfo(int chapterId, bool includeWordCounts = false)
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["chapterId"])]
|
||||
public async Task<ActionResult<BookInfoDto>> GetBookInfo(int chapterId)
|
||||
{
|
||||
var dto = await _unitOfWork.ChapterRepository.GetChapterInfoDtoAsync(chapterId);
|
||||
if (dto == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
|
||||
var bookTitle = string.Empty;
|
||||
IDictionary<int, int>? pageWordCounts = null;
|
||||
|
||||
switch (dto.SeriesFormat)
|
||||
{
|
||||
@ -57,11 +56,6 @@ public class BookController : BaseApiController
|
||||
using var book = await EpubReader.OpenBookAsync(mangaFile.FilePath, BookService.LenientBookReaderOptions);
|
||||
bookTitle = book.Title;
|
||||
|
||||
if (includeWordCounts)
|
||||
{
|
||||
// TODO: Cache this in temp/chapterId folder to avoid having to process file each time
|
||||
pageWordCounts = await _bookService.GetWordCountsPerPage(mangaFile.FilePath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MangaFormat.Pdf:
|
||||
@ -94,7 +88,6 @@ public class BookController : BaseApiController
|
||||
LibraryId = dto.LibraryId,
|
||||
IsSpecial = dto.IsSpecial,
|
||||
Pages = dto.Pages,
|
||||
PageWordCounts = pageWordCounts
|
||||
};
|
||||
|
||||
|
||||
|
@ -15,6 +15,7 @@ using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.Services.Plus;
|
||||
using API.Services.Tasks.Metadata;
|
||||
using API.SignalR;
|
||||
using Hangfire;
|
||||
using Kavita.Common;
|
||||
@ -222,7 +223,6 @@ public class ReaderController : BaseApiController
|
||||
/// <param name="chapterId"></param>
|
||||
/// <param name="extractPdf">Should Kavita extract pdf into images. Defaults to false.</param>
|
||||
/// <param name="includeDimensions">Include file dimensions. Only useful for image-based reading</param>
|
||||
/// <param name="includeWordCounts">Include epub word counts per page. Only useful for epub-based reading</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("chapter-info")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["chapterId", "extractPdf", "includeDimensions"])]
|
||||
@ -849,10 +849,18 @@ public class ReaderController : BaseApiController
|
||||
// Patch in the reading progress
|
||||
await _unitOfWork.ChapterRepository.AddChapterModifiers(User.GetUserId(), chapter);
|
||||
|
||||
// TODO: We need to actually use word count from the pages
|
||||
if (series.Format == MangaFormat.Epub)
|
||||
{
|
||||
var progressCount = chapter.WordCount;
|
||||
// Get the word counts for all the pages
|
||||
var pageCounts = await _bookService.GetWordCountsPerPage(chapter.Files.First().FilePath); // TODO: Cache
|
||||
if (pageCounts == null) return _readerService.GetTimeEstimate(series.WordCount, 0, true);
|
||||
|
||||
// Sum character counts only for pages that have been read
|
||||
var totalCharactersRead = pageCounts
|
||||
.Where(kvp => kvp.Key <= chapter.PagesRead)
|
||||
.Sum(kvp => kvp.Value);
|
||||
|
||||
var progressCount = WordCountAnalyzerService.GetWordCount(totalCharactersRead);
|
||||
var wordsLeft = series.WordCount - progressCount;
|
||||
return _readerService.GetTimeEstimate(wordsLeft, 0, true);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs.Reader;
|
||||
|
||||
@ -16,9 +15,4 @@ public sealed record BookInfoDto : IChapterInfoDto
|
||||
public int Pages { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
public string ChapterTitle { get; set; } = default! ;
|
||||
/// <summary>
|
||||
/// For Epub reader, this will contain Page number -> word count. All other times will be null.
|
||||
/// </summary>
|
||||
/// <remarks>This is optionally returned by includeWordCounts</remarks>
|
||||
public IDictionary<int, int>? PageWordCounts { get; set; }
|
||||
}
|
||||
|
@ -247,7 +247,6 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
|
||||
_unitOfWork.MangaFileRepository.Update(file);
|
||||
}
|
||||
|
||||
|
||||
private async Task<int> GetWordCountFromHtml(EpubLocalTextContentFileRef bookFile, string filePath)
|
||||
{
|
||||
try
|
||||
@ -256,7 +255,8 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
|
||||
doc.LoadHtml(await bookFile.ReadContentAsync());
|
||||
|
||||
var textNodes = doc.DocumentNode.SelectNodes("//body//text()[not(parent::script)]");
|
||||
return textNodes?.Sum(node => node.InnerText.Count(char.IsLetter)) / AverageCharactersPerWord ?? 0;
|
||||
var characterCount = textNodes?.Sum(node => node.InnerText.Count(char.IsLetter)) ?? 0;
|
||||
return GetWordCount(characterCount);
|
||||
}
|
||||
catch (EpubContentException ex)
|
||||
{
|
||||
@ -267,4 +267,10 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetWordCount(int characterCount)
|
||||
{
|
||||
if (characterCount == 0) return 0;
|
||||
return characterCount / AverageCharactersPerWord;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {TranslocoService} from "@jsverse/transloco";
|
||||
import {HourEstimateRange} from "../_models/series-detail/hour-estimate-range";
|
||||
import {DecimalPipe} from "@angular/common";
|
||||
|
||||
@Pipe({
|
||||
name: 'readTimeLeft',
|
||||
@ -11,10 +10,10 @@ export class ReadTimeLeftPipe implements PipeTransform {
|
||||
|
||||
constructor(private readonly translocoService: TranslocoService) {}
|
||||
|
||||
transform(readingTimeLeft: HourEstimateRange): string {
|
||||
transform(readingTimeLeft: HourEstimateRange, includeLeftLabel = false): string {
|
||||
const hoursLabel = readingTimeLeft.avgHours > 1
|
||||
? this.translocoService.translate('read-time-pipe.hours')
|
||||
: this.translocoService.translate('read-time-pipe.hour');
|
||||
? this.translocoService.translate(`read-time-pipe.hours${includeLeftLabel ? '-left' : ''}`)
|
||||
: this.translocoService.translate(`read-time-pipe.hour${includeLeftLabel ? '-left' : ''}`);
|
||||
|
||||
const formattedHours = this.customRound(readingTimeLeft.avgHours);
|
||||
|
||||
|
@ -122,7 +122,7 @@
|
||||
}
|
||||
|
||||
|
||||
<div class="book-title col-2 d-none d-sm-block">
|
||||
<div class="book-title col-3 d-none d-sm-block">
|
||||
@if(isLoading) {
|
||||
<!--Just render a blank div here-->
|
||||
} @else {
|
||||
@ -139,9 +139,9 @@
|
||||
@let timeLeft = readingTimeLeftResource.value();
|
||||
@if (timeLeft) {
|
||||
,
|
||||
<span class="time-left" [ngbTooltip]="t('time-left-alt')">
|
||||
<span class="time-left">
|
||||
<i class="fa-solid fa-clock" aria-hidden="true"></i>
|
||||
{{timeLeft! | readTimeLeft }}
|
||||
{{timeLeft! | readTimeLeft:true }}
|
||||
</span>
|
||||
}
|
||||
|
||||
|
@ -246,12 +246,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
*/
|
||||
nextPageDisabled = false;
|
||||
|
||||
/**
|
||||
* Internal property used to capture all the different css properties to render on all elements. This is a cached version that is updated from reader-settings component
|
||||
*/
|
||||
//pageStyles = model<PageStyle>(this.readerSettingsService.getDefaultPageStyles());
|
||||
//pageStyles!: PageStyle;
|
||||
|
||||
/**
|
||||
* Offset for drawer and rendering canvas. Fixed to 62px.
|
||||
*/
|
||||
@ -270,7 +264,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
request: () => ({
|
||||
chapterId: this.chapterId,
|
||||
seriesId: this.seriesId,
|
||||
pageNumber: this.pageNum()
|
||||
pageNumber: this.pageNum(),
|
||||
}),
|
||||
loader: async ({ request }) => {
|
||||
return this.readerService.getTimeLeftForChapter(this.seriesId, this.chapterId).toPromise();
|
||||
@ -420,16 +414,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
return this.pageNum() === 0 && (currentVirtualPage === 0);
|
||||
}
|
||||
|
||||
|
||||
// get VerticalBookContentWidth() {
|
||||
// if (this.layoutMode() !== BookPageLayoutMode.Default && this.writingStyle() !== WritingStyle.Horizontal ) {
|
||||
// const width = this.getVerticalPageWidth()
|
||||
// return width + 'px';
|
||||
// }
|
||||
// return '';
|
||||
// }
|
||||
|
||||
|
||||
|
||||
get PageWidthForPagination() {
|
||||
if (this.layoutMode() === BookPageLayoutMode.Default && this.writingStyle() === WritingStyle.Vertical && this.horizontalScrollbarNeeded) {
|
||||
|
@ -6,8 +6,4 @@ export interface BookInfo {
|
||||
seriesId: number;
|
||||
libraryId: number;
|
||||
volumeId: number;
|
||||
/**
|
||||
* Maps the page number to character count. Only available on epub reader.
|
||||
*/
|
||||
pageWordCounts: {[key: number]: number};
|
||||
}
|
||||
|
@ -2785,7 +2785,9 @@
|
||||
"read-time-pipe": {
|
||||
"less-than-hour": "<1 Hour",
|
||||
"hour": "Hour",
|
||||
"hours": "Hours"
|
||||
"hours": "Hours",
|
||||
"hour-left": "Hour left",
|
||||
"hours-left": "Hours left"
|
||||
},
|
||||
|
||||
"metadata-setting-field-pipe": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user