From fba3d453041596126c4aa90c2b416a790b0dd699 Mon Sep 17 00:00:00 2001 From: Christian Weiske Date: Thu, 29 Apr 2021 22:45:54 +0200 Subject: [PATCH] Initial working version --- .gitignore | 2 + README.rst | 16 +++ matroskatags.dtd | 32 +++++ tmdb2mkvtags.config.php.dist | 3 + tmdb2mkvtags.php | 240 +++++++++++++++++++++++++++++++++++ 5 files changed, 293 insertions(+) create mode 100644 .gitignore create mode 100644 README.rst create mode 100644 matroskatags.dtd create mode 100644 tmdb2mkvtags.config.php.dist create mode 100755 tmdb2mkvtags.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d7a44e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tmdb2mkvtags.config.php +*.xml diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..3748850 --- /dev/null +++ b/README.rst @@ -0,0 +1,16 @@ +============ +tmdb2mkvtags +============ + +Generate a Matroska tags file from TMDb__ information. + +__ https://www.themoviedb.org/ + + +Usage +===== + +1. Copy ``tmdb2mkvtags.config.php.dist`` to ``tmdb2mkvtags.config.php`` and add your API token +2. Run tmdb2mkvtags.php:: + + php tmdb2mkvtags.php de "James Bond Diamantenfieber" diamanten.xml diff --git a/matroskatags.dtd b/matroskatags.dtd new file mode 100644 index 0000000..5779fa6 --- /dev/null +++ b/matroskatags.dtd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/tmdb2mkvtags.config.php.dist b/tmdb2mkvtags.config.php.dist new file mode 100644 index 0000000..c988c4e --- /dev/null +++ b/tmdb2mkvtags.config.php.dist @@ -0,0 +1,3 @@ + diff --git a/tmdb2mkvtags.php b/tmdb2mkvtags.php new file mode 100755 index 0000000..9cd3822 --- /dev/null +++ b/tmdb2mkvtags.php @@ -0,0 +1,240 @@ +#!/usr/bin/env php + + */ +if ($argc < 3) { + fwrite(STDERR, "Usage: tmdb2mkvtags.php LANGUAGE \"MOVIE TITLE\" [OUTFILE]\n"); + exit(1); +} + +$apiToken = null; +$language = $argv[1]; +$title = $argv[2]; +$outfile = null; +if ($argc == 4) { + $outfile = $argv[3]; +} + +$configFile = preg_replace('#.php$#', '', $argv[0]) . '.config.php'; +if (file_exists($configFile)) { + require_once $configFile; +} +if ($apiToken === null) { + fwrite(STDERR, "API token is not set\n"); + exit(2); +} + + +$movies = queryTmdb( + '/3/search/movie' + . '?query=' . urlencode($title) + . '&language=' . urlencode($language) + . '&include_adult=1' +); + +if ($movies->total_results == 0) { + fwrite(STDERR, "No movies found\n"); + exit(20); + +} else if ($movies->total_results == 1) { + $movie = $movies->results[0]; + +} else { + $page = 1; + $itemsPerPage = 0; + do { + fwrite(STDERR, sprintf("Found %d movies\n", $movies->total_results)); + foreach ($movies->results as $key => $movie) { + fwrite(STDERR, sprintf("[%2d] %s\n", $key + ($page -1) * $itemsPerPage, $movie->title)); + } + if ($page > 1) { + fwrite(STDERR, "p: previous page\n"); + } + if ($movies->total_pages > $page) { + $itemsPerPage = count($movies->results); + fwrite(STDERR, "n: next page\n"); + } + fwrite(STDERR, "\n"); + fwrite(STDERR, 'Your selection: '); + $cmd = readline(); + if (is_numeric($cmd)) { + $num = $cmd - ($page - 1) * $itemsPerPage; + if (isset($movies->results[$num])) { + $movie = $movies->results[$num]; + break; + } + fwrite(STDERR, "Invalid selection $num\n"); + } else if ($cmd == 'n' && $movies->total_pages > $page) { + $page++; + } else if ($cmd == 'p' && $page > 1) { + $page--; + } else if ($cmd == 'q' || $cmd == 'quit' || $cmd == 'exit') { + exit(30); + } + + $movies = queryTmdb( + '/3/search/movie' + . '?query=' . urlencode($title) + . '&language=' . urlencode($language) + . '&include_adult=1' + . '&page=' . $page + ); + } while (true); +} + + +$details = queryTmdb('3/movie/' . $movie->id . '?language=' . $language); +$credits = queryTmdb('3/movie/' . $movie->id . '/credits?language=' . $language); + + +$xml = new MkvTagXMLWriter(); +if ($outfile === null) { + $xml->openMemory(); +} else { + $xml->openURI($outfile); +} +$xml->setIndent(true); +$xml->startDocument("1.0"); +$xml->writeRaw("\n"); +$xml->startElement("Tags"); +$xml->startElement("Tag"); + +$xml->targetType(50); +$xml->simple('TITLE', $movie->title, $language); +if ($language != $movie->original_language) { + $xml->simple('TITLE', $movie->original_title, $movie->original_language); +} +$xml->simple('SUBTITLE', $details->tagline, $language); +$xml->simple('SYNOPSIS', $movie->overview, $language); + +$xml->simple('DATE_RELEASED', $movie->release_date); + +foreach ($details->genres as $genre) { + $xml->simple('GENRE', $genre->name, $language); +} + +$xml->simple('RATING', $movie->vote_average / 2);//0-10 on TMDB, 0-5 mkv +$xml->simple('TMDB', 'movie/' . $movie->id); +$xml->simple('IMDB', $details->imdb_id); + +foreach ($credits->cast as $actor) { + $xml->actor($actor->name, $actor->character, $language); +} + +//map tmdb job to matroska tags +$crewMap = [ + 'Art Direction' => 'ART_DIRECTOR', + 'Costume Design' => 'COSTUME_DESIGNER', + 'Director of Photography' => 'DIRECTOR_OF_PHOTOGRAPHY', + 'Director' => 'DIRECTOR', + 'Editor' => 'EDITED_BY', + 'Novel' => 'WRITTEN_BY', + 'Original Music Composer' => 'COMPOSER', + 'Producer' => 'PRODUCER', + 'Screenplay' => 'WRITTEN_BY', + 'Sound' => 'COMPOSER', + 'Theme Song Performance' => 'LEAD_PERFORMER', +]; +foreach ($credits->crew as $crewmate) { + if (isset($crewMap[$crewmate->job])) { + $xml->simple($crewMap[$crewmate->job], $crewmate->name); + } +} + + +$xml->endElement();//Tag +$xml->endElement();//Tags +$xml->endDocument(); + +if ($outfile === null) { + echo $xml->outputMemory(); +} else { + $xml->flush(); +} + + +//var_dump($credits); +//var_dump($movie, $details); + + +//$tmdbConfig = queryTmdb('3/configuration'); + + +function queryTmdb($path) +{ + global $apiToken; + + $url = 'https://api.themoviedb.org/' . $path; + $ctx = stream_context_create( + [ + 'http' => [ + 'timeout' => 5, + 'ignore_errors' => true, + 'header' => 'Authorization: Bearer ' . $apiToken + ] + ] + ); + $res = file_get_contents($url, false, $ctx); + list(, $statusCode) = explode(' ', $http_response_header[0]); + $data = json_decode($res); + + if ($statusCode != 200) { + if (isset($data->status_code) && isset($data->status_message)) { + throw new Exception( + 'API error: ' . $data->status_code . ' ' . $data->status_message + ); + } + throw new Exception('Error querying API: ' . $statusCode, $statusCode); + } + return $data; +} + +class MkvTagXMLWriter extends XMLWriter +{ + public function actor($actorName, $characterName) + { + $this->startElement('Simple'); + $this->startElement('Name'); + $this->text('ACTOR'); + $this->endElement(); + $this->startElement('String'); + $this->text($actorName); + $this->endElement(); + $this->simple('CHARACTER', $characterName); + $this->endElement();//Simple + } + + public function simple($key, $value, $language = null) + { + $this->startElement('Simple'); + $this->startElement('Name'); + $this->text($key); + $this->endElement(); + $this->startElement('String'); + $this->text($value); + $this->endElement(); + if ($language) { + $this->startElement('TagLanguage'); + $this->text($language); + $this->endElement(); + } + $this->endElement(); + } + + public function targetType($value) + { + $this->startElement('Targets'); + $this->startElement('TargetType'); + $this->text($value); + $this->endElement(); + $this->endElement(); + } +} +?> -- 2.30.2