Beispiel #1
0
// Downloads entire FLV episodes to our temp directory
func (episode *CrunchyrollEpisode) DownloadEpisode(quality, tempDir string, cookies []*http.Cookie) error {
	// Attempts to dump the FLV of the episode to file / will retry up to 5 times
	err := episode.dumpEpisodeFLV(tempDir)
	if err != nil {
		return err
	}

	// Finally renames the dumped FLV to an MKV
	if err := anirip.Rename(tempDir+string(os.PathSeparator)+"incomplete.episode.flv", tempDir+string(os.PathSeparator)+"episode.mkv", 10); err != nil {
		return err
	}
	return nil
}
Beispiel #2
0
// Cleans the MKVs metadata for better reading by clients
func cleanMKV(tempDir string) error {
	// Rename to temp filename before execution
	if err := anirip.Rename(tempDir+string(os.PathSeparator)+"episode.mkv", tempDir+string(os.PathSeparator)+"dirty.episode.mkv", 10); err != nil {
		return err
	}

	// Executes the clean of our temporary dirty mkv
	cmd := exec.Command(anirip.FindAbsoluteBinary("mkclean"),
		"dirty.episode.mkv",
		"episode.mkv")
	cmd.Dir = tempDir
	if err := cmd.Run(); err != nil {
		return anirip.Error{Message: "There was an error while cleaning video", Err: err}
	}

	// Removes the temporary files we created as they are no longer needed
	os.Remove(tempDir + string(os.PathSeparator) + "dirty.episode.mkv")
	return nil
}
Beispiel #3
0
// Merges a VIDEO.mkv and a VIDEO.ass
func mergeSubtitles(audioLang, subtitleLang, tempDir string) error {
	// Removes a stale temp files to avoid conflcts in func
	os.Remove(tempDir + string(os.PathSeparator) + "unmerged.episode.mkv")

	// Rename to temp filename before execution
	if err := anirip.Rename(tempDir+string(os.PathSeparator)+"episode.mkv", tempDir+string(os.PathSeparator)+"unmerged.episode.mkv", 10); err != nil {
		return err
	}

	// Creates the command which we will use to merge our subtitles and video
	cmd := new(exec.Cmd)
	if subtitleLang == "" {
		cmd = exec.Command(anirip.FindAbsoluteBinary("ffmpeg"),
			"-i", "unmerged.episode.mkv",
			"-c:v", "copy",
			"-c:a", "copy",
			"-metadata:s:a:0", "language="+audioLang, // sets audio language to passed audioLang
			"-y", "episode.mkv")
	} else {
		cmd = exec.Command(anirip.FindAbsoluteBinary("ffmpeg"),
			"-i", "unmerged.episode.mkv",
			"-f", "ass",
			"-i", "subtitles.episode.ass",
			"-c:v", "copy",
			"-c:a", "copy",
			"-metadata:s:a:0", "language="+audioLang, // sets audio language to passed audioLang
			"-metadata:s:s:0", "language="+subtitleLang, // sets subtitle language to subtitleLang
			"-disposition:s:0", "default",
			"-y", "episode.mkv")
	}
	cmd.Dir = tempDir

	// Executes the command
	if err := cmd.Run(); err != nil {
		return anirip.Error{Message: "There was an error while merging subtitles", Err: err}
	}

	// Removes old temp files
	os.Remove(tempDir + string(os.PathSeparator) + "subtitles.episode.ass")
	os.Remove(tempDir + string(os.PathSeparator) + "unmerged.episode.mkv")
	return nil
}
Beispiel #4
0
func main() {
	username := ""
	password := ""
	language := "English"
	quality := "1080p"
	trim := ""
	daisukiIntroTrim := false
	aniplexIntroTrim := false
	sunriseIntroTrim := false

	app := cli.NewApp()
	app.Name = "anirip"
	app.Author = "Steven Wolfe"
	app.Email = "*****@*****.**"
	app.Version = "v1.4.0(7/7/2016)"
	app.Usage = "Crunchyroll/Daisuki show ripper CLI"
	color.Cyan(app.Name + " " + app.Version + " - by " + app.Author + " <" + app.Email + ">\n")
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:        "lang, l",
			Value:       "english",
			Usage:       "desired subtitle language",
			Destination: &language,
		},
		cli.StringFlag{
			Name:        "quality, q",
			Value:       "1080p",
			Usage:       "desired video quality",
			Destination: &quality,
		},
		cli.StringFlag{
			Name:        "trim, t",
			Value:       "",
			Usage:       "desired intros to be trimmed off of final video",
			Destination: &trim,
		},
	}
	app.Commands = []cli.Command{
		{
			Name:    "login",
			Aliases: []string{"l"},
			Usage:   "creates and stores cookies for a stream provider",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "user, u",
					Value:       "myusername",
					Usage:       "premium username used to access video stream",
					Destination: &username,
				},
				cli.StringFlag{
					Name:        "pass, p",
					Value:       "mypassword",
					Usage:       "premium password used to access video stream",
					Destination: &password,
				},
			},
			Action: func(c *cli.Context) error {
				// Gets the provider name from the cli argument
				provider := ""
				if c.NArg() > 0 {
					provider = c.Args()[0]
				} else {
					color.Red("[anirip] No provider given...")
					return anirip.Error{Message: "No provider given"}
				}

				// Creates session with cookies to store in file
				var session anirip.Session
				if strings.Contains(provider, "crunchyroll") {
					color.Cyan("[anirip] Logging to CrunchyRoll as " + username + "...")
					session = new(crunchyroll.CrunchyrollSession)
				} else if strings.Contains(provider, "daisuki") {
					color.Cyan("[anirip] Logging to Daisuki as " + username + "...")
					session = new(daisuki.DaisukiSession)
				} else {
					color.Red("[anirip] The given provider is not supported.")
					return anirip.Error{Message: "The given provider is not supported"}
				}

				// Performs the login procedure, storing the login information to file
				if err := session.Login(username, password, tempDir); err != nil {
					color.Red("[anirip] " + err.Error())
					return anirip.Error{Message: "Unable to login to provider", Err: err}
				}
				color.Green("[anirip] Successfully logged in... Cookies saved to " + tempDir)
				return nil
			},
		},
		{
			Name:    "clear",
			Aliases: []string{"c"},
			Usage:   "erases the temporary directory used for cookies and temp files",
			Action: func(c *cli.Context) error {
				// Attempts to erase the temporary directory
				if err := os.RemoveAll(tempDir); err != nil {
					color.Red("[anirip] There was an error erasing the temporary directory : " + err.Error())
					return anirip.Error{Message: "There was an error erasing the temporary directory", Err: err}
				}
				color.Green("[anirip] Successfully erased the temporary directory " + tempDir)
				return nil
			},
		},
	}
	app.Action = func(c *cli.Context) error {
		if c.NArg() == 0 {
			color.Red("[anirip] No show URLs provided.")
			return anirip.Error{Message: "No show URLs provided"}
		}

		for _, showURL := range c.Args() {
			// Parses the URL so we can accurately judge the provider based on the host
			url, err := url.Parse(showURL)
			if err != nil {
				color.Red("[anirip] There was an error parsing the URL you entered.\n")
				return anirip.Error{Message: "There was an error parsing the URL you entered"}
			}

			// Creates the authentication & show objects for the provider we're ripping from
			var session anirip.Session
			var show anirip.Show
			if strings.Contains(strings.ToLower(url.Host), "crunchyroll") {
				show = new(crunchyroll.CrunchyrollShow)
				session = new(crunchyroll.CrunchyrollSession)
			} else if strings.Contains(strings.ToLower(url.Host), "daisuki") {
				show = new(daisuki.DaisukiShow)
				session = new(daisuki.DaisukiSession)
			} else {
				color.Red("[anirip] The URL provided is not supported.")
				return anirip.Error{Message: "The URL provided is not supported"}
			}

			// Performs the generic login procedure
			if err = session.Login(username, password, tempDir); err != nil {
				color.Red("[anirip] " + err.Error())
				return anirip.Error{Message: "Unable to login to provider", Err: err}
			}

			// Attempts to scrape the shows metadata/information
			color.White("[anirip] Getting a list of episodes for the show...")
			if err = show.ScrapeEpisodes(showURL, session.GetCookies()); err != nil {
				color.Red("[anirip] " + err.Error())
				return anirip.Error{Message: "Unable to get episodes", Err: err}
			}

			// Sets the boolean values for what intros we would like to trim
			if strings.Contains(strings.ToLower(trim), "daisuki") {
				daisukiIntroTrim = true
			}
			if strings.Contains(strings.ToLower(trim), "aniplex") {
				daisukiIntroTrim = true
			}
			if strings.Contains(strings.ToLower(trim), "sunrise") {
				daisukiIntroTrim = true
			}

			seasonMap := map[int]string{
				0:  "Specials",
				1:  "Season One",
				2:  "Season Two",
				3:  "Season Three",
				4:  "Season Four",
				5:  "Season Five",
				6:  "Season Six",
				7:  "Season Seven",
				8:  "Season Eight",
				9:  "Season Nine",
				10: "Season Ten",
			}

			os.Mkdir(show.GetTitle(), 0777)
			for _, season := range show.GetSeasons() {
				os.Mkdir(show.GetTitle()+string(os.PathSeparator)+seasonMap[season.GetNumber()], 0777)
				for _, episode := range season.GetEpisodes() {
					color.White("[anirip] Getting Episode Info...\n")
					if err = episode.GetEpisodeInfo(quality, session.GetCookies()); err != nil {
						color.Red("[anirip] " + err.Error())
						continue
					}

					// Checks to see if the episode already exists, in which case we continue to the next
					_, err = os.Stat(show.GetTitle() + string(os.PathSeparator) + seasonMap[season.GetNumber()] + string(os.PathSeparator) + episode.GetFileName() + ".mkv")
					if err == nil {
						color.Green("[anirip] " + episode.GetFileName() + ".mkv has already been downloaded successfully..." + "\n")
						continue
					}

					subOffset := 0
					color.Cyan("[anirip] Downloading " + episode.GetFileName() + "\n")
					// Downloads full MKV video from stream provider
					color.White("[anirip] Downloading video...\n")
					if err := episode.DownloadEpisode(quality, tempDir, session.GetCookies()); err != nil {
						color.Red("[anirip] " + err.Error() + "\n")
						continue
					}

					// Trims down the downloaded MKV if the user wants to trim a Daisuki intro
					if daisukiIntroTrim {
						subOffset = subOffset + daisukiIntroLength
						color.White("[anirip] Trimming off Daisuki Intro - " + strconv.Itoa(daisukiIntroLength) + "ms\n")
						if err := trimMKV(daisukiIntroLength, tempDir); err != nil {
							color.Red("[anirip] " + err.Error() + "\n")
							continue
						}
					}

					// Trims down the downloaded MKV if the user wants to trim an Aniplex intro
					if aniplexIntroTrim {
						subOffset = subOffset + aniplexIntroLength
						color.White("[anirip] Trimming off Aniplex Intro - " + strconv.Itoa(aniplexIntroLength) + "ms\n")
						if err := trimMKV(aniplexIntroLength, tempDir); err != nil {
							color.Red("[anirip] " + err.Error() + "\n")
							continue
						}
					}

					// Trims down the downloaded MKV if the user wants to trim a Sunrise intro
					if sunriseIntroTrim {
						subOffset = subOffset + sunriseIntroLength
						color.White("[anirip] Trimming off Sunrise Intro - " + strconv.Itoa(sunriseIntroLength) + "ms\n")
						if err := trimMKV(sunriseIntroLength, tempDir); err != nil {
							color.Red("[anirip] " + err.Error() + "\n")
							continue
						}
					}

					// Downloads the subtitles to .ass format and
					// offsets their times by the passed provided interval
					color.White("[anirip] Downloading subtitles with a total offset of " + strconv.Itoa(subOffset) + "ms...\n")
					subtitleLang, err := episode.DownloadSubtitles(language, subOffset, tempDir, session.GetCookies())
					if err != nil {
						color.Red("[anirip] " + err.Error() + "\n")
						continue
					}

					// Attempts to merge the downloaded subtitles into the video strea
					color.White("[anirip] Merging subtitles into mkv container...\n")
					if err := mergeSubtitles("jpn", subtitleLang, tempDir); err != nil {
						color.Red("[anirip] " + err.Error() + "\n")
						continue
					}

					// Cleans the MKVs metadata for better reading by clients
					color.White("[anirip] Cleaning MKV...\n")
					if err := cleanMKV(tempDir); err != nil {
						color.Red("[anirip] " + err.Error() + "\n")
						continue
					}

					// Moves the episode to the appropriate season sub-directory
					if err := anirip.Rename(tempDir+string(os.PathSeparator)+"episode.mkv",
						show.GetTitle()+string(os.PathSeparator)+seasonMap[season.GetNumber()]+string(os.PathSeparator)+episode.GetFileName()+".mkv", 10); err != nil {
						color.Red(err.Error() + "\n\n")
					}
					color.Green("[anirip] Downloading and merging completed successfully.\n")
				}
			}
			color.Cyan("[anirip] Completed processing episodes for " + show.GetTitle() + "\n")
		}
		return nil
	}
	app.Run(os.Args)
}
Beispiel #5
0
// Trims the first couple seconds off of the video to remove any logos
func trimMKV(adLength int, tempDir string) error {
	// Removes a stale temp files to avoid conflcts in func
	os.Remove(tempDir + string(os.PathSeparator) + "untrimmed.episode.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "split.episode-001.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "prefix.episode.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "split.episode-002.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "list.episode.txt")

	// Rename to temp filename before execution
	if err := anirip.Rename(tempDir+string(os.PathSeparator)+"episode.mkv", tempDir+string(os.PathSeparator)+"untrimmed.episode.mkv", 10); err != nil {
		return err
	}

	// Executes the command too split the meat of the video from the first ad chunk
	cmd := exec.Command(anirip.FindAbsoluteBinary("mkvmerge"),
		"--split", "timecodes:"+anirip.MStoTimecode(adLength),
		"-o", "split.episode.mkv",
		"untrimmed.episode.mkv")
	cmd.Dir = tempDir
	if err := cmd.Run(); err != nil {
		return anirip.Error{Message: "There was an error while splitting the episode", Err: err}
	}

	// Executes the fine intro trim and waits for the command to finish
	cmd = exec.Command(anirip.FindAbsoluteBinary("ffmpeg"),
		"-i", "split.episode-001.mkv",
		"-ss", anirip.MStoTimecode(adLength), // Exact timestamp of the ad endings
		"-c:v", "h264",
		"-crf", "15",
		"-preset", "slow",
		"-c:a", "copy", "-y", // Use AAC as audio codec to match video.mkv
		"prefix.episode.mkv")
	cmd.Dir = tempDir
	if err := cmd.Run(); err != nil {
		return anirip.Error{Message: "There was an error while creating the prefix clip", Err: err}
	}

	// Creates a text file containing the file names of the 2 files created above
	fileListBytes := []byte("file 'prefix.episode.mkv'\r\nfile 'split.episode-002.mkv'")
	if err := ioutil.WriteFile(tempDir+string(os.PathSeparator)+"list.episode.txt", fileListBytes, 0644); err != nil {
		return anirip.Error{Message: "There was an error while creating list.episode.txt", Err: err}
	}

	// Executes the merge of our two temporary files
	cmd = exec.Command(anirip.FindAbsoluteBinary("ffmpeg"),
		"-f", "concat",
		"-i", "list.episode.txt",
		"-c", "copy", "-y",
		"episode.mkv")
	cmd.Dir = tempDir
	if err := cmd.Run(); err != nil {
		return anirip.Error{Message: "There was an error while merging video and prefix", Err: err}
	}

	// Removes the temporary files we created as they are no longer needed
	os.Remove(tempDir + string(os.PathSeparator) + "untrimmed.episode.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "split.episode-001.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "prefix.episode.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "split.episode-002.mkv")
	os.Remove(tempDir + string(os.PathSeparator) + "list.episode.txt")
	return nil
}