mirror of
https://github.com/crunchy-labs/crunchy-cli.git
synced 2026-01-21 12:12:00 -06:00
Fix typos & add more comments
This commit is contained in:
parent
2e9ce3cf52
commit
3617955bc5
16 changed files with 82 additions and 68 deletions
|
|
@ -73,7 +73,7 @@ _Before reading_: Because of the huge functionality not all cases can be covered
|
|||
|
||||
### Login
|
||||
|
||||
Before you can do something, you have to login first.
|
||||
Before you can do something, you have to log in first.
|
||||
|
||||
This can be performed via crunchyroll account email and password.
|
||||
|
||||
|
|
|
|||
|
|
@ -728,7 +728,7 @@ func (tc *tarCompress) Close() error {
|
|||
err = tc.dst.Close()
|
||||
|
||||
if err != nil && err2 != nil {
|
||||
// best way to show double errors at once that i've found
|
||||
// best way to show double errors at once that I've found
|
||||
return fmt.Errorf("%v\n%v", err, err2)
|
||||
} else if err == nil && err2 != nil {
|
||||
err = err2
|
||||
|
|
@ -750,11 +750,11 @@ func (tc *tarCompress) NewFile(information formatInformation) (io.WriteCloser, e
|
|||
ModTime: time.Now(),
|
||||
Mode: 0644,
|
||||
Typeflag: tar.TypeReg,
|
||||
// fun fact: i did not set the size for quiet some time because i thought that it isn't
|
||||
// required. well because of this i debugged this part for multiple hours because without
|
||||
// fun fact: I did not set the size for quiet some time because I thought that it isn't
|
||||
// required. well because of this I debugged this part for multiple hours because without
|
||||
// proper size information only a tiny amount gets copied into the tar (or zip) writer.
|
||||
// this is also the reason why the file content is completely copied into a buffer before
|
||||
// writing it to the writer. i could bypass this and save some memory but this requires
|
||||
// writing it to the writer. I could bypass this and save some memory but this requires
|
||||
// some rewriting and im nearly at the (planned) finish for version 2 so nah in the future
|
||||
// maybe
|
||||
Size: int64(buf.Len()),
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ func (dp *downloadProgress) update(msg string, permanent bool) {
|
|||
pre := fmt.Sprintf("%s%s [", dp.Prefix, msg)
|
||||
post := fmt.Sprintf("]%4d%% %8d/%d", int(percentage), dp.Current, dp.Total)
|
||||
|
||||
// i don't really know why +2 is needed here but without it the Printf below would not print to the line end
|
||||
// I don't really know why +2 is needed here but without it the Printf below would not print to the line end
|
||||
progressWidth := terminalWidth() - len(pre) - len(post) + 2
|
||||
repeatCount := int(percentage / float32(100) * float32(progressWidth))
|
||||
// it can be lower than zero when the terminal is very tiny
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
// LOCALE represents a locale / language
|
||||
// LOCALE represents a locale / language.
|
||||
type LOCALE string
|
||||
|
||||
const (
|
||||
|
|
@ -31,16 +31,16 @@ const (
|
|||
)
|
||||
|
||||
type Crunchyroll struct {
|
||||
// Client is the http.Client to perform all requests over
|
||||
// Client is the http.Client to perform all requests over.
|
||||
Client *http.Client
|
||||
// Context can be used to stop requests with Client and is context.Background by default
|
||||
// Context can be used to stop requests with Client and is context.Background by default.
|
||||
Context context.Context
|
||||
// Locale specifies in which language all results should be returned / requested
|
||||
// Locale specifies in which language all results should be returned / requested.
|
||||
Locale LOCALE
|
||||
// SessionID is the crunchyroll session id which was used for authentication
|
||||
// SessionID is the crunchyroll session id which was used for authentication.
|
||||
SessionID string
|
||||
|
||||
// Config stores parameters which are needed by some api calls
|
||||
// Config stores parameters which are needed by some api calls.
|
||||
Config struct {
|
||||
TokenType string
|
||||
AccessToken string
|
||||
|
|
@ -56,11 +56,11 @@ type Crunchyroll struct {
|
|||
MaturityRating string
|
||||
}
|
||||
|
||||
// If cache is true, internal caching is enabled
|
||||
// If cache is true, internal caching is enabled.
|
||||
cache bool
|
||||
}
|
||||
|
||||
// LoginWithCredentials logs in via crunchyroll username or email and password
|
||||
// LoginWithCredentials logs in via crunchyroll username or email and password.
|
||||
func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) {
|
||||
sessionIDEndpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?version=1.0&access_token=%s&device_type=%s&device_id=%s",
|
||||
"LNDJgOit5yaRIWN", "com.crunchyroll.windows.desktop", "Az2srGnChW65fuxYz2Xxl1GcZQgtGgI")
|
||||
|
|
@ -87,7 +87,7 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h
|
|||
}
|
||||
|
||||
// LoginWithSessionID logs in via a crunchyroll session id.
|
||||
// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com
|
||||
// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com.
|
||||
func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error) {
|
||||
crunchy := &Crunchyroll{
|
||||
Client: client,
|
||||
|
|
@ -205,7 +205,7 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*
|
|||
return crunchy, nil
|
||||
}
|
||||
|
||||
// request is a base function which handles api requests
|
||||
// request is a base function which handles api requests.
|
||||
func (c *Crunchyroll) request(endpoint string) (*http.Response, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
|
|
@ -241,7 +241,7 @@ func (c *Crunchyroll) request(endpoint string) (*http.Response, error) {
|
|||
}
|
||||
|
||||
// IsCaching returns if data gets cached or not.
|
||||
// See SetCaching for more information
|
||||
// See SetCaching for more information.
|
||||
func (c *Crunchyroll) IsCaching() bool {
|
||||
return c.cache
|
||||
}
|
||||
|
|
@ -249,12 +249,12 @@ func (c *Crunchyroll) IsCaching() bool {
|
|||
// SetCaching enables or disables internal caching of requests made.
|
||||
// Caching is enabled by default.
|
||||
// If it is disabled the already cached data still gets called.
|
||||
// The best way to prevent this is to create a complete new Crunchyroll struct
|
||||
// The best way to prevent this is to create a complete new Crunchyroll struct.
|
||||
func (c *Crunchyroll) SetCaching(caching bool) {
|
||||
c.cache = caching
|
||||
}
|
||||
|
||||
// Search searches a query and returns all found series and movies within the given limit
|
||||
// Search searches a query and returns all found series and movies within the given limit.
|
||||
func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) {
|
||||
searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s",
|
||||
query, limit, c.Locale)
|
||||
|
|
@ -397,7 +397,7 @@ func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, w
|
|||
return
|
||||
}
|
||||
|
||||
// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season
|
||||
// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season.
|
||||
func ParseBetaSeriesURL(url string) (seasonId string, ok bool) {
|
||||
pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?series/(?P<seasonId>\w+).*`)
|
||||
if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 {
|
||||
|
|
@ -408,7 +408,7 @@ func ParseBetaSeriesURL(url string) (seasonId string, ok bool) {
|
|||
return
|
||||
}
|
||||
|
||||
// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode
|
||||
// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode.
|
||||
func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) {
|
||||
pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?watch/(?P<episodeId>\w+).*`)
|
||||
if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import (
|
|||
)
|
||||
|
||||
// NewDownloader creates a downloader with default settings which should
|
||||
// fit the most needs
|
||||
// fit the most needs.
|
||||
func NewDownloader(context context.Context, writer io.Writer, goroutines int, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error) Downloader {
|
||||
tmp, _ := os.MkdirTemp("", "crunchy_")
|
||||
|
||||
|
|
@ -43,12 +43,12 @@ type Downloader struct {
|
|||
// The files will be placed directly into the root of the directory.
|
||||
// If empty a random temporary directory on the system's default tempdir
|
||||
// will be created.
|
||||
// If the directory does not exist, it will be created
|
||||
// If the directory does not exist, it will be created.
|
||||
TempDir string
|
||||
// If DeleteTempAfter is true, the temp directory gets deleted afterwards.
|
||||
// Note that in case of a hard signal exit (os.Interrupt, ...) the directory
|
||||
// will NOT be deleted. In such situations try to catch the signal and
|
||||
// cancel Context
|
||||
// cancel Context.
|
||||
DeleteTempAfter bool
|
||||
|
||||
// Context to control the download process with.
|
||||
|
|
@ -56,7 +56,7 @@ type Downloader struct {
|
|||
// process. So it is not recommend stopping the program immediately after calling
|
||||
// the cancel function. It's better when canceling it and then exit the program
|
||||
// when Format.Download throws an error. See the signal handling section in
|
||||
// cmd/crunchyroll-go/cmd/download.go for an example
|
||||
// cmd/crunchyroll-go/cmd/download.go for an example.
|
||||
Context context.Context
|
||||
|
||||
// Goroutines is the number of goroutines to download segments with
|
||||
|
|
@ -65,11 +65,11 @@ type Downloader struct {
|
|||
// A method to call when a segment was downloaded.
|
||||
// Note that the segments are downloaded asynchronously (depending on the count of
|
||||
// Goroutines) and the function gets called asynchronously too, so for example it is
|
||||
// first called on segment 1, then segment 254, then segment 3 and so on
|
||||
// first called on segment 1, then segment 254, then segment 3 and so on.
|
||||
OnSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error
|
||||
// If LockOnSegmentDownload is true, only one OnSegmentDownload function can be called at
|
||||
// once. Normally (because of the use of goroutines while downloading) multiple could get
|
||||
// called simultaneously
|
||||
// called simultaneously.
|
||||
LockOnSegmentDownload bool
|
||||
|
||||
// If FFmpegOpts is not nil, ffmpeg will be used to merge and convert files.
|
||||
|
|
@ -82,7 +82,7 @@ type Downloader struct {
|
|||
FFmpegOpts []string
|
||||
}
|
||||
|
||||
// download's the given format
|
||||
// download's the given format.
|
||||
func (d Downloader) download(format *Format) error {
|
||||
if err := format.InitVideo(); err != nil {
|
||||
return err
|
||||
|
|
@ -109,7 +109,7 @@ func (d Downloader) download(format *Format) error {
|
|||
}
|
||||
|
||||
// mergeSegments reads every file in tempDir and writes their content to Downloader.Writer.
|
||||
// The given output file gets created or overwritten if already existing
|
||||
// The given output file gets created or overwritten if already existing.
|
||||
func (d Downloader) mergeSegments(files []string) error {
|
||||
for _, file := range files {
|
||||
select {
|
||||
|
|
@ -132,7 +132,7 @@ func (d Downloader) mergeSegments(files []string) error {
|
|||
|
||||
// mergeSegmentsFFmpeg reads every file in tempDir and merges their content to the outputFile
|
||||
// with ffmpeg (https://ffmpeg.org/).
|
||||
// The given output file gets created or overwritten if already existing
|
||||
// The given output file gets created or overwritten if already existing.
|
||||
func (d Downloader) mergeSegmentsFFmpeg(files []string) error {
|
||||
list, err := os.Create(filepath.Join(d.TempDir, "list.txt"))
|
||||
if err != nil {
|
||||
|
|
@ -214,13 +214,13 @@ func (d Downloader) mergeSegmentsFFmpeg(files []string) error {
|
|||
// After every segment download onSegmentDownload will be called with:
|
||||
// the downloaded segment, the current position, the total size of segments to download,
|
||||
// the file where the segment content was written to an error (if occurred).
|
||||
// The filename is always <number of downloaded segment>.ts
|
||||
// The filename is always <number of downloaded segment>.ts.
|
||||
//
|
||||
// Short explanation:
|
||||
// The actual crunchyroll video is split up in multiple segments (or video files) which
|
||||
// have to be downloaded and merged after to generate a single video file.
|
||||
// And this function just downloads each of this segment into the given directory.
|
||||
// See https://en.wikipedia.org/wiki/MPEG_transport_stream for more information
|
||||
// See https://en.wikipedia.org/wiki/MPEG_transport_stream for more information.
|
||||
func (d Downloader) downloadSegments(format *Format) ([]string, error) {
|
||||
if err := format.InitVideo(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -318,7 +318,7 @@ func (d Downloader) downloadSegments(format *Format) ([]string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// getCrypt extracts the key and iv of a m3u8 segment and converts it into a cipher.Block and an iv byte sequence
|
||||
// getCrypt extracts the key and iv of a m3u8 segment and converts it into a cipher.Block and an iv byte sequence.
|
||||
func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, iv []byte, err error) {
|
||||
var resp *http.Response
|
||||
|
||||
|
|
@ -341,7 +341,7 @@ func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, i
|
|||
return block, iv, nil
|
||||
}
|
||||
|
||||
// downloadSegment downloads a segment, decrypts it and names it after the given index
|
||||
// downloadSegment downloads a segment, decrypts it and names it after the given index.
|
||||
func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment, filename string, block cipher.Block, iv []byte) (*os.File, error) {
|
||||
// every segment is aes-128 encrypted and has to be decrypted when downloaded
|
||||
content, err := d.decryptSegment(format.crunchy.Client, segment, block, iv)
|
||||
|
|
@ -361,7 +361,7 @@ func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment,
|
|||
return file, nil
|
||||
}
|
||||
|
||||
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L25
|
||||
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L25.
|
||||
func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegment, block cipher.Block, iv []byte) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(d.Context, http.MethodGet, segment.URI, nil)
|
||||
if err != nil {
|
||||
|
|
@ -387,7 +387,7 @@ func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegme
|
|||
return raw, nil
|
||||
}
|
||||
|
||||
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L47
|
||||
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L47.
|
||||
func (d Downloader) pkcs5UnPadding(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unPadding := int(origData[length-1])
|
||||
|
|
|
|||
10
episode.go
10
episode.go
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Episode contains all information about an episode.
|
||||
type Episode struct {
|
||||
crunchy *Crunchyroll
|
||||
|
||||
|
|
@ -74,7 +75,7 @@ type Episode struct {
|
|||
StreamID string
|
||||
}
|
||||
|
||||
// EpisodeFromID returns an episode by its api id
|
||||
// EpisodeFromID returns an episode by its api id.
|
||||
func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) {
|
||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
crunchy.Config.CountryCode,
|
||||
|
|
@ -111,7 +112,8 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) {
|
|||
|
||||
// AudioLocale returns the audio locale of the episode.
|
||||
// Every episode in a season (should) have the same audio locale,
|
||||
// so if you want to get the audio locale of a season, just call this method on the first episode of the season
|
||||
// so if you want to get the audio locale of a season, just call
|
||||
// this method on the first episode of the season.
|
||||
func (e *Episode) AudioLocale() (LOCALE, error) {
|
||||
streams, err := e.Streams()
|
||||
if err != nil {
|
||||
|
|
@ -120,7 +122,7 @@ func (e *Episode) AudioLocale() (LOCALE, error) {
|
|||
return streams[0].AudioLocale, nil
|
||||
}
|
||||
|
||||
// GetFormat returns the format which matches the given resolution and subtitle locale
|
||||
// GetFormat returns the format which matches the given resolution and subtitle locale.
|
||||
func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) {
|
||||
streams, err := e.Streams()
|
||||
if err != nil {
|
||||
|
|
@ -186,7 +188,7 @@ func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*
|
|||
return nil, fmt.Errorf("no matching resolution found")
|
||||
}
|
||||
|
||||
// Streams returns all streams which are available for the episode
|
||||
// Streams returns all streams which are available for the episode.
|
||||
func (e *Episode) Streams() ([]*Stream, error) {
|
||||
if e.children != nil {
|
||||
return e.children, nil
|
||||
|
|
|
|||
2
error.go
2
error.go
|
|
@ -3,7 +3,7 @@ package crunchyroll
|
|||
import "fmt"
|
||||
|
||||
// AccessError is an error which will be returned when some special sort of api request fails.
|
||||
// See Crunchyroll.request when the error gets used
|
||||
// See Crunchyroll.request when the error gets used.
|
||||
type AccessError struct {
|
||||
error
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ const (
|
|||
MOVIE = "movies"
|
||||
)
|
||||
|
||||
// Format contains detailed information about an episode video stream.
|
||||
type Format struct {
|
||||
crunchy *Crunchyroll
|
||||
|
||||
ID string
|
||||
// FormatType represents if the format parent is an episode or a movie
|
||||
// FormatType represents if the format parent is an episode or a movie.
|
||||
FormatType FormatType
|
||||
Video *m3u8.Variant
|
||||
AudioLocale LOCALE
|
||||
|
|
@ -27,7 +28,7 @@ type Format struct {
|
|||
// The Format.Video.Chunklist pointer is, by default, nil because an additional
|
||||
// request must be made to receive its content. The request is not made when
|
||||
// initializing a Format struct because it would probably cause an intense overhead
|
||||
// since Format.Video.Chunklist is only used sometimes
|
||||
// since Format.Video.Chunklist is only used sometimes.
|
||||
func (f *Format) InitVideo() error {
|
||||
if f.Video.Chunklist == nil {
|
||||
resp, err := f.crunchy.Client.Get(f.Video.URI)
|
||||
|
|
@ -45,7 +46,7 @@ func (f *Format) InitVideo() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Download downloads the Format with the via Downloader specified options
|
||||
// Download downloads the Format with the via Downloader specified options.
|
||||
func (f *Format) Download(downloader Downloader) error {
|
||||
return downloader.download(f)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// MovieListing contains information about something which is called
|
||||
// movie listing. I don't know what this means thb.
|
||||
type MovieListing struct {
|
||||
crunchy *Crunchyroll
|
||||
|
||||
|
|
@ -36,7 +38,7 @@ type MovieListing struct {
|
|||
AvailabilityNotes string `json:"availability_notes"`
|
||||
}
|
||||
|
||||
// MovieListingFromID returns a movie listing by its api id
|
||||
// MovieListingFromID returns a movie listing by its api id.
|
||||
func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) {
|
||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
crunchy.Config.CountryCode,
|
||||
|
|
@ -65,7 +67,7 @@ func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error)
|
|||
return movieListing, nil
|
||||
}
|
||||
|
||||
// AudioLocale is same as Episode.AudioLocale
|
||||
// AudioLocale is same as Episode.AudioLocale.
|
||||
func (ml *MovieListing) AudioLocale() (LOCALE, error) {
|
||||
resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
ml.crunchy.Config.CountryCode,
|
||||
|
|
@ -86,7 +88,7 @@ func (ml *MovieListing) AudioLocale() (LOCALE, error) {
|
|||
return LOCALE(jsonBody["audio_locale"].(string)), nil
|
||||
}
|
||||
|
||||
// Streams returns all streams which are available for the movie listing
|
||||
// Streams returns all streams which are available for the movie listing.
|
||||
func (ml *MovieListing) Streams() ([]*Stream, error) {
|
||||
return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
ml.crunchy.Config.CountryCode,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"regexp"
|
||||
)
|
||||
|
||||
// Season contains information about an anime season.
|
||||
type Season struct {
|
||||
crunchy *Crunchyroll
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ type Season struct {
|
|||
SubtitleLocales []LOCALE
|
||||
}
|
||||
|
||||
// SeasonFromID returns a season by its api id
|
||||
// SeasonFromID returns a season by its api id.
|
||||
func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) {
|
||||
resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
crunchy.Config.CountryCode,
|
||||
|
|
@ -70,6 +71,7 @@ func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) {
|
|||
return season, nil
|
||||
}
|
||||
|
||||
// AudioLocale returns the audio locale of the season.
|
||||
func (s *Season) AudioLocale() (LOCALE, error) {
|
||||
episodes, err := s.Episodes()
|
||||
if err != nil {
|
||||
|
|
@ -78,7 +80,7 @@ func (s *Season) AudioLocale() (LOCALE, error) {
|
|||
return episodes[0].AudioLocale()
|
||||
}
|
||||
|
||||
// Episodes returns all episodes which are available for the season
|
||||
// Episodes returns all episodes which are available for the season.
|
||||
func (s *Season) Episodes() (episodes []*Episode, err error) {
|
||||
if s.children != nil {
|
||||
return s.children, nil
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"regexp"
|
||||
)
|
||||
|
||||
// Stream contains information about all available video stream of an episode.
|
||||
type Stream struct {
|
||||
crunchy *Crunchyroll
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ type Stream struct {
|
|||
streamURL string
|
||||
}
|
||||
|
||||
// StreamsFromID returns a stream by its api id
|
||||
// StreamsFromID returns a stream by its api id.
|
||||
func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) {
|
||||
return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
crunchy.Config.CountryCode,
|
||||
|
|
@ -35,7 +36,7 @@ func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) {
|
|||
crunchy.Config.KeyPairID))
|
||||
}
|
||||
|
||||
// Formats returns all formats which are available for the stream
|
||||
// Formats returns all formats which are available for the stream.
|
||||
func (s *Stream) Formats() ([]*Format, error) {
|
||||
if s.children != nil {
|
||||
return s.children, nil
|
||||
|
|
@ -70,7 +71,7 @@ func (s *Stream) Formats() ([]*Format, error) {
|
|||
return formats, nil
|
||||
}
|
||||
|
||||
// fromVideoStreams returns all streams which are accessible via the endpoint
|
||||
// fromVideoStreams returns all streams which are accessible via the endpoint.
|
||||
func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, err error) {
|
||||
resp, err := crunchy.request(endpoint)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
// Subtitle contains the information about a video subtitle.
|
||||
type Subtitle struct {
|
||||
crunchy *Crunchyroll
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ type Subtitle struct {
|
|||
Format string `json:"format"`
|
||||
}
|
||||
|
||||
// Save writes the subtitle to the given io.Writer.
|
||||
func (s Subtitle) Save(writer io.Writer) error {
|
||||
req, err := http.NewRequestWithContext(s.crunchy.Context, http.MethodGet, s.URL, nil)
|
||||
if err != nil {
|
||||
|
|
|
|||
4
url.go
4
url.go
|
|
@ -5,7 +5,7 @@ import (
|
|||
)
|
||||
|
||||
// ExtractEpisodesFromUrl extracts all episodes from an url.
|
||||
// If audio is not empty, the episodes gets filtered after the given locale
|
||||
// If audio is not empty, the episodes gets filtered after the given locale.
|
||||
func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Episode, error) {
|
||||
series, episodes, err := c.ParseUrl(url)
|
||||
if err != nil {
|
||||
|
|
@ -78,7 +78,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep
|
|||
}
|
||||
|
||||
// ParseUrl parses the given url into a series or episode.
|
||||
// The returning episode is a slice because non-beta urls have the same episode with different languages
|
||||
// The returning episode is a slice because non-beta urls have the same episode with different languages.
|
||||
func (c *Crunchyroll) ParseUrl(url string) (*Series, []*Episode, error) {
|
||||
if seriesId, ok := ParseBetaSeriesURL(url); ok {
|
||||
series, err := SeriesFromID(c, seriesId)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/ByteDream/crunchyroll-go"
|
||||
)
|
||||
|
||||
// AllLocales is an array of all available locales.
|
||||
var AllLocales = []crunchyroll.LOCALE{
|
||||
crunchyroll.JP,
|
||||
crunchyroll.US,
|
||||
|
|
@ -18,7 +19,7 @@ var AllLocales = []crunchyroll.LOCALE{
|
|||
crunchyroll.AR,
|
||||
}
|
||||
|
||||
// ValidateLocale validates if the given locale actually exist
|
||||
// ValidateLocale validates if the given locale actually exist.
|
||||
func ValidateLocale(locale crunchyroll.LOCALE) bool {
|
||||
for _, l := range AllLocales {
|
||||
if l == locale {
|
||||
|
|
@ -28,7 +29,7 @@ func ValidateLocale(locale crunchyroll.LOCALE) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// LocaleLanguage returns the country by its locale
|
||||
// LocaleLanguage returns the country by its locale.
|
||||
func LocaleLanguage(locale crunchyroll.LOCALE) string {
|
||||
switch locale {
|
||||
case crunchyroll.JP:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
// SortEpisodesBySeason sorts the given episodes by their seasons.
|
||||
// Note that the same episodes just with different audio locales will cause problems
|
||||
// Note that the same episodes just with different audio locales will cause problems.
|
||||
func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Episode {
|
||||
sortMap := map[string]map[int][]*crunchyroll.Episode{}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Epis
|
|||
return eps
|
||||
}
|
||||
|
||||
// SortEpisodesByAudio sort the given episodes by their audio locale
|
||||
// SortEpisodesByAudio sort the given episodes by their audio locale.
|
||||
func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCALE][]*crunchyroll.Episode, error) {
|
||||
eps := map[crunchyroll.LOCALE][]*crunchyroll.Episode{}
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCAL
|
|||
return eps, nil
|
||||
}
|
||||
|
||||
// MovieListingsByDuration sorts movie listings by their duration
|
||||
// MovieListingsByDuration sorts movie listings by their duration.
|
||||
type MovieListingsByDuration []*crunchyroll.MovieListing
|
||||
|
||||
func (mlbd MovieListingsByDuration) Len() int {
|
||||
|
|
@ -94,7 +94,7 @@ func (mlbd MovieListingsByDuration) Less(i, j int) bool {
|
|||
return mlbd[i].DurationMS < mlbd[j].DurationMS
|
||||
}
|
||||
|
||||
// EpisodesByDuration sorts episodes by their duration
|
||||
// EpisodesByDuration sorts episodes by their duration.
|
||||
type EpisodesByDuration []*crunchyroll.Episode
|
||||
|
||||
func (ebd EpisodesByDuration) Len() int {
|
||||
|
|
@ -107,6 +107,7 @@ func (ebd EpisodesByDuration) Less(i, j int) bool {
|
|||
return ebd[i].DurationMS < ebd[j].DurationMS
|
||||
}
|
||||
|
||||
// EpisodesByNumber sorts episodes after their episode number.
|
||||
type EpisodesByNumber []*crunchyroll.Episode
|
||||
|
||||
func (ebn EpisodesByNumber) Len() int {
|
||||
|
|
@ -119,7 +120,7 @@ func (ebn EpisodesByNumber) Less(i, j int) bool {
|
|||
return ebn[i].EpisodeNumber < ebn[j].EpisodeNumber
|
||||
}
|
||||
|
||||
// FormatsByResolution sorts formats after their resolution
|
||||
// FormatsByResolution sorts formats after their resolution.
|
||||
type FormatsByResolution []*crunchyroll.Format
|
||||
|
||||
func (fbr FormatsByResolution) Len() int {
|
||||
|
|
@ -140,6 +141,7 @@ func (fbr FormatsByResolution) Less(i, j int) bool {
|
|||
return iResX+iResY < jResX+jResY
|
||||
}
|
||||
|
||||
// SubtitlesByLocale sorts subtitles after their locale.
|
||||
type SubtitlesByLocale []*crunchyroll.Subtitle
|
||||
|
||||
func (sbl SubtitlesByLocale) Len() int {
|
||||
|
|
|
|||
15
video.go
15
video.go
|
|
@ -30,8 +30,10 @@ type video struct {
|
|||
} `json:"images"`
|
||||
}
|
||||
|
||||
// Video is the base for Movie and Season.
|
||||
type Video interface{}
|
||||
|
||||
// Movie contains information about a movie.
|
||||
type Movie struct {
|
||||
video
|
||||
Video
|
||||
|
|
@ -40,7 +42,7 @@ type Movie struct {
|
|||
|
||||
children []*MovieListing
|
||||
|
||||
// not generated when calling MovieFromID
|
||||
// not generated when calling MovieFromID.
|
||||
MovieListingMetadata struct {
|
||||
AvailabilityNotes string `json:"availability_notes"`
|
||||
AvailableOffline bool `json:"available_offline"`
|
||||
|
|
@ -65,7 +67,7 @@ type Movie struct {
|
|||
}
|
||||
}
|
||||
|
||||
// MovieFromID returns a movie by its api id
|
||||
// MovieFromID returns a movie by its api id.
|
||||
func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) {
|
||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
crunchy.Config.CountryCode,
|
||||
|
|
@ -95,8 +97,6 @@ func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) {
|
|||
}
|
||||
|
||||
// MovieListing returns all videos corresponding with the movie.
|
||||
// Beside the normal movie, sometimes movie previews are returned too, but you can try to get the actual movie
|
||||
// by sorting the returning MovieListing slice with the utils.MovieListingByDuration interface
|
||||
func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) {
|
||||
if m.children != nil {
|
||||
return m.children, nil
|
||||
|
|
@ -134,6 +134,7 @@ func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) {
|
|||
return movieListings, nil
|
||||
}
|
||||
|
||||
// Series contains information about an anime series.
|
||||
type Series struct {
|
||||
video
|
||||
Video
|
||||
|
|
@ -156,13 +157,13 @@ type Series struct {
|
|||
MatureRatings []string `json:"mature_ratings"`
|
||||
SeasonCount int `json:"season_count"`
|
||||
|
||||
// not generated when calling SeriesFromID
|
||||
// not generated when calling SeriesFromID.
|
||||
SearchMetadata struct {
|
||||
Score float64 `json:"score"`
|
||||
}
|
||||
}
|
||||
|
||||
// SeriesFromID returns a series by its api id
|
||||
// SeriesFromID returns a series by its api id.
|
||||
func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) {
|
||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||
crunchy.Config.CountryCode,
|
||||
|
|
@ -191,7 +192,7 @@ func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) {
|
|||
return series, nil
|
||||
}
|
||||
|
||||
// Seasons returns all seasons of a series
|
||||
// Seasons returns all seasons of a series.
|
||||
func (s *Series) Seasons() (seasons []*Season, err error) {
|
||||
if s.children != nil {
|
||||
return s.children, nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue