feat: add ability to change folder naming convention

This commit is contained in:
Simon Ding
2024-09-20 20:03:05 +08:00
parent f5c977224b
commit 9968f9f225
8 changed files with 131 additions and 39 deletions

View File

@@ -15,6 +15,8 @@ const (
SettingAllowQiangban = "filter_qiangban" SettingAllowQiangban = "filter_qiangban"
SettingEnableTmdbAdultContent = "tmdb_adult_content" SettingEnableTmdbAdultContent = "tmdb_adult_content"
SetttingSizeLimiter = "size_limiter" SetttingSizeLimiter = "size_limiter"
SettingTvNamingFormat = "tv_naming_format"
SettingMovieNamingFormat = "movie_naming_format"
) )
const ( const (
@@ -35,6 +37,15 @@ const (
LanguageCN = "zh-CN" LanguageCN = "zh-CN"
) )
const DefaultNamingFormat = "{{.NameCN}} {{.NameEN}} ({{.Year}})"
type NamingInfo struct {
NameCN string
NameEN string
Year string
TmdbID int
}
type ResolutionType string type ResolutionType string
const JwtSerectKey = "jwt_secrect_key" const JwtSerectKey = "jwt_secrect_key"

View File

@@ -628,3 +628,19 @@ func (c *Client) SetSizeLimiter(limiter *SizeLimiter) error {
} }
return c.SetSetting(SetttingSizeLimiter, string(data)) return c.SetSetting(SetttingSizeLimiter, string(data))
} }
func (c *Client) GetTvNamingFormat() string {
s := c.GetSetting(SettingTvNamingFormat)
if s == "" {
return DefaultNamingFormat
}
return s
}
func (c *Client) GetMovingNamingFormat() string {
s := c.GetSetting(SettingMovieNamingFormat)
if s == "" {
return DefaultNamingFormat
}
return s
}

View File

@@ -1,7 +1,9 @@
package core package core
import ( import (
"bytes"
"fmt" "fmt"
"html/template"
"io" "io"
"net/http" "net/http"
"os" "os"
@@ -366,27 +368,35 @@ func (c *Client) SuggestedMovieFolderName(tmdbId int) (string, error) {
return javid, nil return javid, nil
} }
} }
info := db.NamingInfo{TmdbID: tmdbId}
//if name is already in english, no need to query again if utils.IsASCII(name) {
if !utils.IsASCII(name) && c.language == db.LanguageCN { info.NameEN = stripExtraCharacters(name)
} else {
info.NameCN = stripExtraCharacters(name)
en, err := c.MustTMDB().GetMovieDetails(tmdbId, db.LanguageEN) en, err := c.MustTMDB().GetMovieDetails(tmdbId, db.LanguageEN)
if err != nil { if err != nil {
log.Errorf("get en movie detail error: %v", err) log.Errorf("get en tv detail error: %v", err)
} else { } else {
name = fmt.Sprintf("%s %s", name, en.Title) info.NameEN = stripExtraCharacters(en.Title)
} }
} }
//remove extra characters
re := regexp.MustCompile(`[^\p{L}\w\s]`)
name = re.ReplaceAllString(name, " ")
name = strings.Join(strings.Fields(name), " ")
year := strings.Split(d1.ReleaseDate, "-")[0] year := strings.Split(d1.ReleaseDate, "-")[0]
if year != "" { info.Year = year
name = fmt.Sprintf("%s (%s)", name, year) movieNamingFormat := c.db.GetMovingNamingFormat()
}
log.Infof("tv series of tmdb id %v suggestting name is %v", tmdbId, name) tmpl, err := template.New("test").Parse(movieNamingFormat)
return name, nil if err != nil {
return "", errors.Wrap(err, "naming format")
}
buff := &bytes.Buffer{}
err = tmpl.Execute(buff, info)
if err != nil {
return "", errors.Wrap(err, "tmpl exec")
}
res := strings.TrimSpace(buff.String())
log.Infof("tv series of tmdb id %v suggestting name is %v", tmdbId, res)
return res, nil
} }
func (c *Client) SuggestedSeriesFolderName(tmdbId int) (string, error) { func (c *Client) SuggestedSeriesFolderName(tmdbId int) (string, error) {
@@ -398,24 +408,40 @@ func (c *Client) SuggestedSeriesFolderName(tmdbId int) (string, error) {
name := d.Name name := d.Name
//if name is already in english, no need to query again info := db.NamingInfo{TmdbID: tmdbId}
if !utils.IsASCII(name) && c.language == db.LanguageCN { if utils.IsASCII(name) {
info.NameEN = stripExtraCharacters(name)
} else {
info.NameCN = stripExtraCharacters(name)
en, err := c.MustTMDB().GetTvDetails(tmdbId, db.LanguageEN) en, err := c.MustTMDB().GetTvDetails(tmdbId, db.LanguageEN)
if err != nil { if err != nil {
log.Errorf("get en tv detail error: %v", err) log.Errorf("get en tv detail error: %v", err)
} else { } else {
name = fmt.Sprintf("%s %s", name, en.Name) info.NameEN = stripExtraCharacters(en.Name)
} }
} }
//remove extra characters
re := regexp.MustCompile(`[^\p{L}\w\s]`)
name = re.ReplaceAllString(name, " ")
name = strings.Join(strings.Fields(name), " ")
year := strings.Split(d.FirstAirDate, "-")[0] year := strings.Split(d.FirstAirDate, "-")[0]
if year != "" { info.Year = year
name = fmt.Sprintf("%s (%s)", name, year)
}
log.Infof("tv series of tmdb id %v suggestting name is %v", tmdbId, name) tvNamingFormat := c.db.GetTvNamingFormat()
return name, nil
tmpl, err := template.New("test").Parse(tvNamingFormat)
if err != nil {
return "", errors.Wrap(err, "naming format")
}
buff := &bytes.Buffer{}
err = tmpl.Execute(buff, info)
if err != nil {
return "", errors.Wrap(err, "tmpl exec")
}
res := strings.TrimSpace(buff.String())
log.Infof("tv series of tmdb id %v suggestting name is %v", tmdbId, res)
return res, nil
}
func stripExtraCharacters(s string) string {
re := regexp.MustCompile(`[^\p{L}\w\s]`)
s = re.ReplaceAllString(s, " ")
return strings.Join(strings.Fields(s), " ")
} }

View File

@@ -22,6 +22,8 @@ type GeneralSettings struct {
EnableNfo bool `json:"enable_nfo"` EnableNfo bool `json:"enable_nfo"`
AllowQiangban bool `json:"allow_qiangban"` AllowQiangban bool `json:"allow_qiangban"`
EnableAdultContent bool `json:"enable_adult_content"` EnableAdultContent bool `json:"enable_adult_content"`
TvNamingFormat string `json:"tv_naming_format"`
MovieNamingFormat string `json:"movie_naming_format"`
} }
func (s *Server) SetSetting(c *gin.Context) (interface{}, error) { func (s *Server) SetSetting(c *gin.Context) (interface{}, error) {
@@ -46,7 +48,12 @@ func (s *Server) SetSetting(c *gin.Context) (interface{}, error) {
if err := s.db.SetSetting(db.SettingLogLevel, in.LogLevel); err != nil { if err := s.db.SetSetting(db.SettingLogLevel, in.LogLevel); err != nil {
return nil, errors.Wrap(err, "save log level") return nil, errors.Wrap(err, "save log level")
} }
}
if in.TvNamingFormat != "" {
s.db.SetSetting(db.SettingTvNamingFormat, in.TvNamingFormat)
}
if in.MovieNamingFormat != "" {
s.db.SetSetting(db.SettingMovieNamingFormat, in.MovieNamingFormat)
} }
plexmatchEnabled := s.db.GetSetting(db.SettingPlexMatchEnabled) plexmatchEnabled := s.db.GetSetting(db.SettingPlexMatchEnabled)
@@ -74,7 +81,6 @@ func (s *Server) SetSetting(c *gin.Context) (interface{}, error) {
} else { } else {
s.db.SetSetting(db.SettingEnableTmdbAdultContent, "false") s.db.SetSetting(db.SettingEnableTmdbAdultContent, "false")
} }
return nil, nil return nil, nil
} }
@@ -86,6 +92,8 @@ func (s *Server) GetSetting(c *gin.Context) (interface{}, error) {
allowQiangban := s.db.GetSetting(db.SettingAllowQiangban) allowQiangban := s.db.GetSetting(db.SettingAllowQiangban)
enableNfo := s.db.GetSetting(db.SettingNfoSupportEnabled) enableNfo := s.db.GetSetting(db.SettingNfoSupportEnabled)
enableAdult := s.db.GetSetting(db.SettingEnableTmdbAdultContent) enableAdult := s.db.GetSetting(db.SettingEnableTmdbAdultContent)
tvFormat := s.db.GetTvNamingFormat()
movieFormat := s.db.GetMovingNamingFormat()
return &GeneralSettings{ return &GeneralSettings{
TmdbApiKey: tmdb, TmdbApiKey: tmdb,
DownloadDir: downloadDir, DownloadDir: downloadDir,
@@ -95,6 +103,8 @@ func (s *Server) GetSetting(c *gin.Context) (interface{}, error) {
AllowQiangban: allowQiangban == "true", AllowQiangban: allowQiangban == "true",
EnableNfo: enableNfo == "true", EnableNfo: enableNfo == "true",
EnableAdultContent: enableAdult == "true", EnableAdultContent: enableAdult == "true",
TvNamingFormat: tvFormat,
MovieNamingFormat: movieFormat,
}, nil }, nil
} }

View File

@@ -138,7 +138,7 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
trailing: selectedTab == 0 trailing: selectedTab == 0
? IconButton( ? IconButton(
tooltip: "删除任务", tooltip: "删除任务",
onPressed: () => onDelete()(ac.id!), onPressed: () => onDelete()(ac.id!.toString()),
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete))
: const Text("-"), : const Text("-"),
), ),

View File

@@ -72,7 +72,7 @@ class Activity {
required this.size, required this.size,
required this.seedRatio}); required this.seedRatio});
final String? id; final int? id;
final int? mediaId; final int? mediaId;
final int? episodeId; final int? episodeId;
final String? sourceTitle; final String? sourceTitle;

View File

@@ -59,6 +59,8 @@ class GeneralSetting {
bool? allowQiangban; bool? allowQiangban;
bool? enableNfo; bool? enableNfo;
bool? enableAdult; bool? enableAdult;
String? tvNamingFormat;
String? movieNamingFormat;
GeneralSetting( GeneralSetting(
{this.tmdbApiKey, {this.tmdbApiKey,
@@ -68,6 +70,8 @@ class GeneralSetting {
this.enablePlexmatch, this.enablePlexmatch,
this.enableNfo, this.enableNfo,
this.allowQiangban, this.allowQiangban,
this.tvNamingFormat,
this.movieNamingFormat,
this.enableAdult}); this.enableAdult});
factory GeneralSetting.fromJson(Map<String, dynamic> json) { factory GeneralSetting.fromJson(Map<String, dynamic> json) {
@@ -79,6 +83,8 @@ class GeneralSetting {
enableAdult: json["enable_adult_content"] ?? false, enableAdult: json["enable_adult_content"] ?? false,
allowQiangban: json["allow_qiangban"] ?? false, allowQiangban: json["allow_qiangban"] ?? false,
enableNfo: json["enable_nfo"] ?? false, enableNfo: json["enable_nfo"] ?? false,
tvNamingFormat: json["tv_naming_format"],
movieNamingFormat: json["movie_naming_format"],
enablePlexmatch: json["enable_plexmatch"] ?? false); enablePlexmatch: json["enable_plexmatch"] ?? false);
} }
@@ -92,6 +98,8 @@ class GeneralSetting {
data["allow_qiangban"] = allowQiangban; data["allow_qiangban"] = allowQiangban;
data["enable_nfo"] = enableNfo; data["enable_nfo"] = enableNfo;
data["enable_adult_content"] = enableAdult; data["enable_adult_content"] = enableAdult;
data["tv_naming_format"] = tvNamingFormat;
data["movie_naming_format"] = movieNamingFormat;
return data; return data;
} }
} }

View File

@@ -38,6 +38,8 @@ class _GeneralState extends ConsumerState<GeneralSettings> {
"allow_qiangban": v.allowQiangban, "allow_qiangban": v.allowQiangban,
"enable_nfo": v.enableNfo, "enable_nfo": v.enableNfo,
"enable_adult": v.enableAdult, "enable_adult": v.enableAdult,
"tv_naming_format": v.tvNamingFormat,
"movie_naming_format": v.movieNamingFormat,
}, },
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -66,6 +68,22 @@ class _GeneralState extends ConsumerState<GeneralSettings> {
hintText: "http://10.0.0.1:1080", hintText: "http://10.0.0.1:1080",
helperText: "后台联网代理地址,留空表示不启用代理"), helperText: "后台联网代理地址,留空表示不启用代理"),
), ),
FormBuilderTextField(
decoration: const InputDecoration(
icon: Icon(Icons.folder),
labelText: "电视剧路径命名规则",
helperText:
"go template语法可用的变量为.NameCN, .NameEN, .Year, .TmdbID"),
name: "tv_naming_format",
),
FormBuilderTextField(
decoration: const InputDecoration(
icon: Icon(Icons.folder),
labelText: "电影路径命名规则",
helperText:
"go template语法可用的变量为.NameCN, .NameEN, .Year, .TmdbID"),
name: "movie_naming_format",
),
SizedBox( SizedBox(
width: 300, width: 300,
child: FormBuilderDropdown( child: FormBuilderDropdown(
@@ -137,6 +155,9 @@ class _GeneralState extends ConsumerState<GeneralSettings> {
allowQiangban: values["allow_qiangban"], allowQiangban: values["allow_qiangban"],
enableAdult: values["enable_adult"], enableAdult: values["enable_adult"],
enableNfo: values["enable_nfo"], enableNfo: values["enable_nfo"],
tvNamingFormat: values["tv_naming_format"],
movieNamingFormat:
values["movie_naming_format"],
enablePlexmatch: enablePlexmatch:
values["enable_plexmatch"])) values["enable_plexmatch"]))
.then((v) => showSnakeBar("更新成功")); .then((v) => showSnakeBar("更新成功"));