diff --git a/server/handler.go b/server/handler.go index 5dd9985..b2a8bd9 100644 --- a/server/handler.go +++ b/server/handler.go @@ -19,28 +19,46 @@ func MovieList(c *gin.Context) { } 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 spage := c.Query("page") if spage != "" { p, err := strconv.Atoi(spage) if err != nil { log.Println(err) + return } page = p - 1 } - var limit int = 10 + var limit int = 12 slimit := c.Query("limit") if slimit != "" { l, err := strconv.Atoi(slimit) if err != nil { log.Println(err) + return } limit = l } start := page * limit var end int = start + limit + + if start > len(movies) { + start = len(movies) + } if end > len(movies) { end = len(movies) } diff --git a/server/main.go b/server/main.go index 00899b9..156130d 100644 --- a/server/main.go +++ b/server/main.go @@ -7,20 +7,31 @@ import ( "net/http" "os/exec" "path/filepath" + "sort" + "strconv" "strings" "github.com/gin-gonic/gin" ) -// Movie 电影结构 -type Movie struct { - Name string `json:"filename"` - Image string `json:"image"` +// Category 电影分类结构 +type Category struct { + Name string `json:"name"` + 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() { + // 需要改进 如果存在这个文件的略缩图, 就不存进movieDict里 var movieDict map[string]string = make(map[string]string) matches, err := filepath.Glob("movie/*") @@ -31,7 +42,7 @@ func initMovie() { for _, filename := range matches { base := filepath.Base(filename) - ext := filepath.Ext(base) + // ext := filepath.Ext(base) base = base[:strings.IndexByte(base, '.')] if _, ok := movieDict[base]; ok { @@ -40,7 +51,6 @@ func initMovie() { movieDict[base] = filename } - log.Println(base, ext) } for key, filename := range movieDict { @@ -48,7 +58,6 @@ func initMovie() { // height := 120 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", "-i", filename, "-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") } - // 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 { if !info.IsDir() && filepath.Ext(info.Name()) != ".png" { 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()) - movies = append(movies, Movie{ - Name: info.Name(), - Image: base[:strings.IndexByte(base, '.')] + ".png", - }) + + movie := Movie{ + 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 }) - 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() { diff --git a/src/CategoryNav.js b/src/CategoryNav.js new file mode 100644 index 0000000..437499c --- /dev/null +++ b/src/CategoryNav.js @@ -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 ( + + {categories.map((category, idx) => ( + + ))} + + ); +}; + +export default CategoryNav; \ No newline at end of file diff --git a/src/Components.js b/src/Components.js index 04d3c69..12705af 100644 --- a/src/Components.js +++ b/src/Components.js @@ -9,13 +9,24 @@ import Typography from '@mui/material/Typography'; import Pagination from '@mui/material/Pagination'; import { Link } from 'react-router-dom'; 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 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({ movies: [], page: 1, @@ -25,24 +36,37 @@ const Main = () => { 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); try { 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) { const data = response.data.data; - setPagination({ - movies: data.items, - page: page, - total: data.total, - length: Math.ceil(data.total / limit), - }); - localStorage.setItem('lastPage', page); + console.log(`${config.Host}/movie/?page=${page}&limit=${limit}&category=${category}`); + if (data.items.length === 0 && page > 1) { + // 如果返回的数据为空且请求的页码大于1,则尝试获取上一页的数据 + fetchMovies(page - 1); + } else { + setPagination({ + movies: data.items, + page: page, + total: data.total, + length: Math.ceil(data.total / limit), + }); + localStorage.setItem('lastPage', page); + } } } catch (error) { console.error('Error fetching movies:', error); @@ -53,11 +77,11 @@ const Main = () => { useEffect(() => { const lastPage = localStorage.getItem('lastPage') || 1; - fetchMovies(lastPage); + fetchMovies(currentCategory, lastPage); }, []); const handlePageChange = (event, value) => { - fetchMovies(value); + fetchMovies(currentCategory, value); }; const truncateFilename = (filename, maxLength) => { @@ -65,9 +89,15 @@ const Main = () => { ? filename.substring(0, maxLength - 3) + '...' : filename; }; + return ( +
{ />
+ +
{loading ? (
{
) : ( + + + - {pagination.movies.map((item) => ( - ( + + - - - - - - {truncateFilename(item.filename, 15)} - - - - - - ))} - + + + + ))} + )}
+ +
{ + const truncateFilename = (filename, maxLength) => { + return filename.length > maxLength + ? filename.substring(0, maxLength - 3) + '...' + : filename; + }; + + return ( + + + + {truncateFilename(movie.filename, 15)} + + 时长: {movie.duration} min + + + + ); +}; + +export default MovieCard; \ No newline at end of file diff --git a/src/VideoPlayer.js b/src/VideoPlayer.js index c03ab0f..2297427 100644 --- a/src/VideoPlayer.js +++ b/src/VideoPlayer.js @@ -16,6 +16,7 @@ const VideoPlayer = () => { {filename}