feat: add name suggesting

This commit is contained in:
Simon Ding
2024-07-19 16:59:12 +08:00
parent 1786e44933
commit 80d802fb4c
8 changed files with 152 additions and 55 deletions

View File

@@ -126,13 +126,6 @@ func (c *Client) AddMediaWatchlist(m *ent.Media, episodes []int) (*ent.Media, er
m.StorageID = r.ID
}
}
targetDir := fmt.Sprintf("%s %s (%v)", m.NameCn, m.NameEn, strings.Split(m.AirDate, "-")[0])
if !utils.IsChineseChar(m.NameCn) {
log.Warnf("name cn is not chinese name: %v", m.NameCn)
targetDir = fmt.Sprintf("%s (%v)", m.NameEn, strings.Split(m.AirDate, "-")[0])
}
r, err := c.ent.Media.Create().
SetTmdbID(m.TmdbID).
SetStorageID(m.StorageID).
@@ -143,7 +136,7 @@ func (c *Client) AddMediaWatchlist(m *ent.Media, episodes []int) (*ent.Media, er
SetMediaType(m.MediaType).
SetAirDate(m.AirDate).
SetResolution(m.Resolution).
SetTargetDir(targetDir).
SetTargetDir(m.TargetDir).
AddEpisodeIDs(episodes...).
Save(context.TODO())
return r, err
@@ -325,6 +318,13 @@ type WebdavSetting struct {
}
func (c *Client) AddStorage(st *StorageInfo) error {
if !strings.HasSuffix(st.Settings["tv_path"], "/") {
st.Settings["tv_path"] += "/"
}
if !strings.HasSuffix(st.Settings["movie_path"], "/") {
st.Settings["movie_path"] += "/"
}
data, err := json.Marshal(st.Settings)
if err != nil {

View File

@@ -76,6 +76,7 @@ func (s *Server) Serve() error {
tv.GET("/record/:id", HttpHandler(s.GetMediaDetails))
tv.DELETE("/record/:id", HttpHandler(s.DeleteFromWatchlist))
tv.GET("/resolutions", HttpHandler(s.GetAvailableResolutions))
tv.GET("/suggest/:tmdb_id", HttpHandler(s.SuggestedSeriesFolderName))
}
indexer := api.Group("/indexer")
{

View File

@@ -4,7 +4,9 @@ import (
"fmt"
"polaris/db"
"polaris/log"
"polaris/pkg/utils"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
@@ -35,3 +37,36 @@ func (s *Server) DeleteStorage(c *gin.Context) (interface{}, error) {
err = s.db.DeleteStorage(id)
return nil, err
}
func (s *Server) SuggestedSeriesFolderName(c *gin.Context) (interface{}, error) {
ids := c.Param("tmdb_id")
id, err := strconv.Atoi(ids)
if err != nil {
return nil, fmt.Errorf("id is not int: %v", ids)
}
var name, originalName, year string
d, err := s.MustTMDB().GetTvDetails(id, s.language)
if err != nil {
d1, err := s.MustTMDB().GetMovieDetails(id, s.language)
if err != nil {
return nil, errors.Wrap(err, "get movie details")
}
name = d1.Title
originalName = d1.OriginalTitle
year = strings.Split(d1.ReleaseDate, "-")[0]
} else {
name = d.Name
originalName = d.OriginalName
year = strings.Split(d.FirstAirDate, "-")[0]
}
name = fmt.Sprintf("%s %s", name, originalName)
if !utils.IsChineseChar(name) {
name = originalName
}
if year != "" {
name = fmt.Sprintf("%s (%s)", name, year)
}
return gin.H{"name": name}, nil
}

View File

@@ -53,6 +53,7 @@ type addWatchlistIn struct {
TmdbID int `json:"tmdb_id" binding:"required"`
StorageID int `json:"storage_id" `
Resolution string `json:"resolution" binding:"required"`
Folder string `json:"folder" binding:"required"`
}
func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) {
@@ -109,6 +110,7 @@ func (s *Server) AddTv2Watchlist(c *gin.Context) (interface{}, error) {
AirDate: detail.FirstAirDate,
Resolution: string(in.Resolution),
StorageID: in.StorageID,
TargetDir: in.Folder,
}, epIds)
if err != nil {
return nil, errors.Wrap(err, "add to list")

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:ui/activity.dart';
import 'package:ui/search.dart';
import 'package:ui/system_settings.dart';
import 'package:ui/welcome_page.dart';

View File

@@ -15,6 +15,7 @@ class APIs {
static final watchlistMovieUrl = "$_baseUrl/api/v1/media/movie/watchlist";
static final availableMoviesUrl = "$_baseUrl/api/v1/media/movie/resources/";
static final seriesDetailUrl = "$_baseUrl/api/v1/media/record/";
static final suggestedTvName = "$_baseUrl/api/v1/media/suggest/";
static final searchAndDownloadUrl = "$_baseUrl/api/v1/indexer/download";
static final allIndexersUrl = "$_baseUrl/api/v1/indexer/";
static final addIndexerUrl = "$_baseUrl/api/v1/indexer/add";

View File

@@ -17,6 +17,18 @@ final tvWatchlistDataProvider = FutureProvider.autoDispose((ref) async {
return favList;
});
final suggestNameDataProvider = FutureProvider.autoDispose.family(
(ref, int arg) async {
final dio = await APIs.getDio();
var resp = await dio.get(APIs.suggestedTvName + arg.toString());
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
return sp.data["name"] as String;
},
);
final movieWatchlistDataProvider = FutureProvider.autoDispose((ref) async {
final dio = await APIs.getDio();
var resp = await dio.get(APIs.watchlistMovieUrl);
@@ -75,14 +87,15 @@ class SearchPageData
state = newState;
}
Future<void> submit2Watchlist(
int tmdbId, int storageId, String resolution, String mediaType) async {
Future<void> submit2Watchlist(int tmdbId, int storageId, String resolution,
String mediaType, String folder) async {
final dio = await APIs.getDio();
if (mediaType == "tv") {
var resp = await dio.post(APIs.watchlistTvUrl, data: {
"tmdb_id": tmdbId,
"storage_id": storageId,
"resolution": resolution
"resolution": resolution,
"folder": folder
});
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
@@ -93,7 +106,8 @@ class SearchPageData
var resp = await dio.post(APIs.watchlistMovieUrl, data: {
"tmdb_id": tmdbId,
"storage_id": storageId,
"resolution": resolution
"resolution": resolution,
"folder": folder
});
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
@@ -116,9 +130,11 @@ class SearchResponse {
page: json["page"],
totalPage: json["total_page"],
totalResults: json["total_results"],
results: json["results"] == null ? []: json["results"]
.map((v) => SearchResult.fromJson(v))
.toList());
results: json["results"] == null
? []
: (json["results"] as List)
.map((v) => SearchResult.fromJson(v))
.toList());
}
}

View File

@@ -98,8 +98,8 @@ class _SearchPageState extends ConsumerState<SearchPage> {
var f = NotificationListener(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo is ScrollEndNotification &&
scrollInfo.metrics.axisDirection == AxisDirection.down &&
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) {
scrollInfo.metrics.axisDirection == AxisDirection.down &&
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) {
ref.read(searchPageDataProvider(q).notifier).queryNextPage();
}
return true;
@@ -136,45 +136,84 @@ class _SearchPageState extends ConsumerState<SearchPage> {
String resSelected = "1080p";
int storageSelected = 0;
var storage = ref.watch(storageSettingProvider);
var name = ref.watch(suggestNameDataProvider(item.id!));
var pathController = TextEditingController();
return AlertDialog(
title: Text('添加剧集: ${item.name}'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
DropdownMenu(
label: const Text("清晰度"),
initialSelection: resSelected,
dropdownMenuEntries: const [
DropdownMenuEntry(value: "720p", label: "720p"),
DropdownMenuEntry(value: "1080p", label: "1080p"),
DropdownMenuEntry(value: "4k", label: "4k"),
],
onSelected: (value) {
setState(() {
resSelected = value!;
});
},
),
storage.when(
data: (v) {
return DropdownMenu(
label: const Text("存储位置"),
initialSelection: storageSelected,
dropdownMenuEntries: v
.map((s) => DropdownMenuEntry(
label: s.name!, value: s.id))
.toList(),
onSelected: (value) {
setState(() {
storageSelected = value!;
});
},
);
content: SizedBox(
width: 500,
height: 200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownMenu(
label: const Text("清晰度"),
initialSelection: resSelected,
dropdownMenuEntries: const [
DropdownMenuEntry(value: "720p", label: "720p"),
DropdownMenuEntry(value: "1080p", label: "1080p"),
DropdownMenuEntry(value: "4k", label: "4k"),
],
onSelected: (value) {
setState(() {
resSelected = value!;
});
},
error: (err, trace) => Text("$err"),
loading: () => const MyProgressIndicator()),
],
),
storage.when(
data: (v) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownMenu(
label: const Text("存储位置"),
initialSelection: storageSelected,
dropdownMenuEntries: v
.map((s) => DropdownMenuEntry(
label: s.name!, value: s.id))
.toList(),
onSelected: (value) {
setState(() {
storageSelected = value!;
});
},
),
name.when(
data: (s) {
final path = item.mediaType == "tv"
? v[storageSelected]
.settings!["tv_path"]
: v[storageSelected]
.settings!["movie_path"];
pathController.text = s;
return SizedBox(
//width: 300,
child: TextField (
controller: pathController,
decoration: InputDecoration(
labelText: "存储路径",
prefix: Text(path)
),
),
);
},
error: (error, stackTrace) => Text("$error"),
loading: () => const MyProgressIndicator(
size: 20,
),
),
],
);
},
error: (err, trace) => Text("$err"),
loading: () => const MyProgressIndicator()),
],
),
),
actions: <Widget>[
TextButton(
@@ -195,8 +234,12 @@ class _SearchPageState extends ConsumerState<SearchPage> {
ref
.read(searchPageDataProvider(widget.query ?? "")
.notifier)
.submit2Watchlist(item.id!, storageSelected,
resSelected, item.mediaType!);
.submit2Watchlist(
item.id!,
storageSelected,
resSelected,
item.mediaType!,
pathController.text);
Navigator.of(context).pop();
},
),