最新分类版本

This commit is contained in:
eson 2023-07-08 17:44:51 +08:00
parent fd0bdd7c12
commit fdf7493195
6 changed files with 215 additions and 70 deletions

View File

@ -19,28 +19,46 @@ func MovieList(c *gin.Context) {
} }
defer c.JSON(0, response) defer c.JSON(0, response)
category := c.Query("category")
var movies []Movie
if category != "" {
cateidx, err := strconv.Atoi(category)
if err != nil {
log.Println(err)
return
}
movies = categories[cateidx].Movies
}
var page int = 0 var page int = 0
spage := c.Query("page") spage := c.Query("page")
if spage != "" { if spage != "" {
p, err := strconv.Atoi(spage) p, err := strconv.Atoi(spage)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return
} }
page = p - 1 page = p - 1
} }
var limit int = 10 var limit int = 12
slimit := c.Query("limit") slimit := c.Query("limit")
if slimit != "" { if slimit != "" {
l, err := strconv.Atoi(slimit) l, err := strconv.Atoi(slimit)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return
} }
limit = l limit = l
} }
start := page * limit start := page * limit
var end int = start + limit var end int = start + limit
if start > len(movies) {
start = len(movies)
}
if end > len(movies) { if end > len(movies) {
end = len(movies) end = len(movies)
} }

View File

@ -7,20 +7,31 @@ import (
"net/http" "net/http"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"sort"
"strconv"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// Movie 电影结构 // Category 电影分类结构
type Movie struct { type Category struct {
Name string `json:"filename"` Name string `json:"name"`
Image string `json:"image"` Movies []Movie `json:"movies"`
} }
var movies []Movie // Movie 电影结构
type Movie struct {
Name string `json:"filename"`
Image string `json:"image"`
Duration int `json:"duration"`
}
// var movies []Movie
var categories []Category
func initMovie() { func initMovie() {
// 需要改进 如果存在这个文件的略缩图, 就不存进movieDict里
var movieDict map[string]string = make(map[string]string) var movieDict map[string]string = make(map[string]string)
matches, err := filepath.Glob("movie/*") matches, err := filepath.Glob("movie/*")
@ -31,7 +42,7 @@ func initMovie() {
for _, filename := range matches { for _, filename := range matches {
base := filepath.Base(filename) base := filepath.Base(filename)
ext := filepath.Ext(base) // ext := filepath.Ext(base)
base = base[:strings.IndexByte(base, '.')] base = base[:strings.IndexByte(base, '.')]
if _, ok := movieDict[base]; ok { if _, ok := movieDict[base]; ok {
@ -40,7 +51,6 @@ func initMovie() {
movieDict[base] = filename movieDict[base] = filename
} }
log.Println(base, ext)
} }
for key, filename := range movieDict { for key, filename := range movieDict {
@ -48,7 +58,6 @@ func initMovie() {
// height := 120 // height := 120
log.Println(filename) log.Println(filename)
// cmd := exec.Command("ffmpeg", "-i", filename, "-vframes", "1", "-s", fmt.Sprintf("%dx%d", width, height), "movie/"+key+".png")
cmd := exec.Command("ffmpeg", cmd := exec.Command("ffmpeg",
"-i", filename, "-i", filename,
"-vf", "select='isnan(prev_selected_t)+gte(t-prev_selected_t\\,35)',scale=320:180,tile=3x3", "-vf", "select='isnan(prev_selected_t)+gte(t-prev_selected_t\\,35)',scale=320:180,tile=3x3",
@ -63,25 +72,60 @@ func initMovie() {
panic("could not generate frame") panic("could not generate frame")
} }
// err := generateThumbnail(filename, "movie/"+key+".png", width, height) }
// if err != nil {
// panic("could not generate frame") // 初始化分类
// } categories = []Category{
{Name: "15min", Movies: []Movie{}},
{Name: "30min", Movies: []Movie{}},
{Name: "60min", Movies: []Movie{}},
{Name: "大于60min", Movies: []Movie{}},
} }
filepath.Walk("./movie", func(path string, info fs.FileInfo, err error) error { filepath.Walk("./movie", func(path string, info fs.FileInfo, err error) error {
if !info.IsDir() && filepath.Ext(info.Name()) != ".png" { if !info.IsDir() && filepath.Ext(info.Name()) != ".png" {
base := info.Name() base := info.Name()
cmd := exec.Command("ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=duration", "-of", "default=nw=1:nk=1", "./movie/"+info.Name())
durationOutput, err := cmd.Output()
if err != nil {
log.Printf("Error getting duration for %s: %v", info.Name(), err)
return err
}
duration, err := strconv.ParseFloat(strings.Trim(string(durationOutput), "\n "), 64)
if err != nil {
log.Printf("Error parsing duration for %s: %v", info.Name(), err)
return err
}
log.Println(path, info.Name()) log.Println(path, info.Name())
movies = append(movies, Movie{
Name: info.Name(), movie := Movie{
Image: base[:strings.IndexByte(base, '.')] + ".png", Name: info.Name(),
}) Image: base[:strings.IndexByte(base, '.')] + ".png",
Duration: int(duration / 60.0),
}
if movie.Duration <= 15 {
categories[0].Movies = append(categories[0].Movies, movie)
} else if movie.Duration <= 30 {
categories[1].Movies = append(categories[1].Movies, movie)
} else if movie.Duration <= 60 {
categories[2].Movies = append(categories[2].Movies, movie)
} else {
categories[3].Movies = append(categories[3].Movies, movie)
}
} }
return nil return nil
}) })
log.Printf("%##v", movies) for _, category := range categories {
var movies = category.Movies
sort.Slice(movies, func(i, j int) bool {
return movies[i].Duration < movies[j].Duration
})
}
// log.Printf("%##v", categories)
} }
func main() { func main() {

25
src/CategoryNav.js Normal file
View File

@ -0,0 +1,25 @@
import React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
const CategoryNav = ({ categories, currentCategory, onCategoryChange }) => {
const handleChange = (event, newValue) => {
onCategoryChange(newValue);
};
return (
<Tabs
value={currentCategory}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
centered
>
{categories.map((category, idx) => (
<Tab key={category.label} label={category.label} value={idx} />
))}
</Tabs>
);
};
export default CategoryNav;

View File

@ -9,13 +9,24 @@ import Typography from '@mui/material/Typography';
import Pagination from '@mui/material/Pagination'; import Pagination from '@mui/material/Pagination';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import ConfigContext from './Config';
import ConfigContext from './Config';
import MovieCard from './MovieCard';
import CategoryNav from './CategoryNav';
const Main = () => { const Main = () => {
const config = useContext(ConfigContext); const config = useContext(ConfigContext);
const [currentCategory, setCurrentCategory] = useState(0);
const categories = [
{ label: '15min', idx: 0 },
{ label: '30min', idx: 1 },
{ label: '60min', idx: 2 },
{ label: '大于60min', idx: 3 },
];
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
movies: [], movies: [],
page: 1, page: 1,
@ -25,24 +36,37 @@ const Main = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const limit = 12; const limit = 20;
const fetchMovies = async (page) => { const handleCategoryChange = (category) => {
setCurrentCategory(category);
fetchMovies(category, 1);
};
const fetchMovies = async (category, page) => {
setLoading(true); setLoading(true);
try { try {
const response = await axios.get( const response = await axios.get(
`${config.Host}/movie/?page=${page}&limit=${limit}` `${config.Host}/movie/?page=${page}&limit=${limit}&category=${category}`
); );
if (response.status === 200) { if (response.status === 200) {
const data = response.data.data; const data = response.data.data;
setPagination({ console.log(`${config.Host}/movie/?page=${page}&limit=${limit}&category=${category}`);
movies: data.items, if (data.items.length === 0 && page > 1) {
page: page, // 如果返回的数据为空且请求的页码大于1则尝试获取上一页的数据
total: data.total, fetchMovies(page - 1);
length: Math.ceil(data.total / limit), } else {
}); setPagination({
localStorage.setItem('lastPage', page); movies: data.items,
page: page,
total: data.total,
length: Math.ceil(data.total / limit),
});
localStorage.setItem('lastPage', page);
}
} }
} catch (error) { } catch (error) {
console.error('Error fetching movies:', error); console.error('Error fetching movies:', error);
@ -53,11 +77,11 @@ const Main = () => {
useEffect(() => { useEffect(() => {
const lastPage = localStorage.getItem('lastPage') || 1; const lastPage = localStorage.getItem('lastPage') || 1;
fetchMovies(lastPage); fetchMovies(currentCategory, lastPage);
}, []); }, []);
const handlePageChange = (event, value) => { const handlePageChange = (event, value) => {
fetchMovies(value); fetchMovies(currentCategory, value);
}; };
const truncateFilename = (filename, maxLength) => { const truncateFilename = (filename, maxLength) => {
@ -65,9 +89,15 @@ const Main = () => {
? filename.substring(0, maxLength - 3) + '...' ? filename.substring(0, maxLength - 3) + '...'
: filename; : filename;
}; };
return ( return (
<Container style={{ marginTop: 20 }}> <Container style={{ marginTop: 20 }}>
<CategoryNav
categories={categories}
currentCategory={currentCategory}
onCategoryChange={handleCategoryChange}
/>
<div style={{ textAlign: 'center', marginBottom: 20 }}> <div style={{ textAlign: 'center', marginBottom: 20 }}>
<Pagination <Pagination
count={pagination.length} count={pagination.length}
@ -79,6 +109,8 @@ const Main = () => {
/> />
</div> </div>
<div> <div>
{loading ? ( {loading ? (
<div <div
@ -92,50 +124,34 @@ const Main = () => {
<CircularProgress /> <CircularProgress />
</div> </div>
) : ( ) : (
<Grid container spacing={2} style={{ marginTop: 3 }}> <Grid container spacing={2} style={{ marginTop: 3 }}>
{pagination.movies.map((item) => ( {pagination.movies.map((item) => (
<Grid <Grid
item item
xs={6} xs={6}
sm={4} sm={4}
md={3} md={3}
lg={2} lg={2}
style={{ display: 'flex', justifyContent: 'space-between' }} style={{ display: 'flex', justifyContent: 'space-between' }}
key={item.filename} key={item.filename}
>
<Link
to={`/res/${item.filename}`}
style={{ textDecoration: 'none', paddingBottom: 10 }}
> >
<Link <MovieCard movie={item} config={config} />
to={`/res/${item.filename}`} </Link>
style={{ textDecoration: 'none', paddingBottom: 10 }} </Grid>
> ))}
<Card </Grid>
style={{
width: '100%',
height: 200,
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
transition: 'box-shadow 0.3s ease-in-out',
'&:hover': {
boxShadow: '0 8px 12px rgba(0, 0, 0, 0.2)',
},
}}
>
<CardMedia
component="img"
height="120"
image={`${config.Host}/res/${item.image}`}
/>
<CardContent>
<Typography>
{truncateFilename(item.filename, 15)}
</Typography>
</CardContent>
</Card>
</Link>
</Grid>
))}
</Grid>
)} )}
</div> </div>
<div style={{ textAlign: 'center', marginTop: 20 }}> <div style={{ textAlign: 'center', marginTop: 20 }}>
<Pagination <Pagination
count={pagination.length} count={pagination.length}

41
src/MovieCard.js Normal file
View File

@ -0,0 +1,41 @@
import React from 'react';
import Card from '@mui/material/Card';
import CardMedia from '@mui/material/CardMedia';
import CardContent from '@mui/material/CardContent';
import Typography from '@mui/material/Typography';
const MovieCard = ({ movie, config }) => {
const truncateFilename = (filename, maxLength) => {
return filename.length > maxLength
? filename.substring(0, maxLength - 3) + '...'
: filename;
};
return (
<Card
style={{
width: '100%',
height: 200,
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
transition: 'box-shadow 0.3s ease-in-out',
'&:hover': {
boxShadow: '0 8px 12px rgba(0, 0, 0, 0.2)',
},
}}
>
<CardMedia
component="img"
height="120"
image={`${config.Host}/res/${movie.image}`}
/>
<CardContent>
<Typography>{truncateFilename(movie.filename, 15)}</Typography>
<Typography variant="body2" color="textSecondary">
时长: {movie.duration} min
</Typography>
</CardContent>
</Card>
);
};
export default MovieCard;

View File

@ -16,6 +16,7 @@ const VideoPlayer = () => {
{filename} {filename}
</Typography> </Typography>
<video <video
autoPlay={true}
controls controls
style={{ width: '100%', maxHeight: '70vh' }} style={{ width: '100%', maxHeight: '70vh' }}
src={`${config.Host}/res/${filename}`} src={`${config.Host}/res/${filename}`}