Compare commits
No commits in common. "bc3a42b5019142594499b45fc8ac34fab9c7c63d" and "b7534c01f554c43479eb9e7a42d6e56040ed08c9" have entirely different histories.
bc3a42b501
...
b7534c01f5
17 changed files with 1180 additions and 3053 deletions
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Services\Trakt;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class TraktDebug extends Command
|
||||
{
|
||||
protected $signature = 'trakt:debug {endpoint : Trakt API endpoint to call} {method=GET : HTTP method to use} {--d|data=* : data}';
|
||||
protected $description = 'Make arbitrary requests to Trakt API.' . PHP_EOL . '-d should be formatted with key=value or key:=value for non-strings';
|
||||
|
||||
public function handle(Trakt $trakt): int
|
||||
{
|
||||
$body = $this->getBody();
|
||||
$method = $this->argument('method');
|
||||
$url = $this->argument('endpoint');
|
||||
|
||||
$resp = $trakt->request()->asJson();
|
||||
if ($body) {
|
||||
$body = json_encode($body);
|
||||
$resp->withBody($body);
|
||||
}
|
||||
|
||||
$this->line("Request: $method $url $body");
|
||||
$resp = $resp->send($method, $url);
|
||||
$this->line('Response (' . $resp->status() . '):' . PHP_EOL . json_encode($resp->json(), JSON_PRETTY_PRINT));
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
|
||||
protected function getBody(): ?array
|
||||
{
|
||||
if ($this->argument('method') === 'GET') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = [];
|
||||
foreach ($this->option('data') as $kv) {
|
||||
[$k, $v] = explode('=', $kv) + [null, null];
|
||||
if (empty($k) || empty($v)) {
|
||||
continue;
|
||||
}
|
||||
if ($k[strlen($k) - 1] === ':') {
|
||||
$k = substr($k, 0, strlen($k) - 1);
|
||||
$v = json_decode($v, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Arr::set($ret, $k, $v);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Data\WatchFile;
|
||||
use App\Data\WatchData;
|
||||
use App\Services\Trakt;
|
||||
|
||||
class TraktWatch extends Command
|
||||
{
|
||||
protected $signature = 'show:watch {file : File to watch}';
|
||||
protected $description = 'Mark a show as watched right now on Trakt';
|
||||
|
||||
public function handle(Trakt $trakt): int
|
||||
{
|
||||
$file = WatchFile::from($this->argument('file'));
|
||||
$data = WatchData::from(['rawData' => [$file->toArray()]]);
|
||||
$resp = $trakt->syncHistory($data, $this->output);
|
||||
|
||||
return $resp->ok() ? static::SUCCESS : static::FAILURE;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Data\WatchData;
|
||||
use App\Services\Trakt;
|
||||
|
||||
class TraktWatchImport extends Command
|
||||
{
|
||||
protected $signature = 'show:watch:import {files* : JSON files to import}';
|
||||
protected $description = 'Once online, sync watches from show:watch:export';
|
||||
|
||||
public function handle(Trakt $trakt): int
|
||||
{
|
||||
$watched = WatchData::from($this->arguments());
|
||||
$resp = $trakt->syncHistory($watched, $this->output);
|
||||
|
||||
return $resp->ok() ? static::SUCCESS : static::FAILURE;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\DataPipes;
|
||||
|
||||
use App\Data\Enums\Type;
|
||||
use FFMpeg\FFMpeg;
|
||||
use Illuminate\Support\Collection;
|
||||
use SimpleXMLElement;
|
||||
use Spatie\LaravelData\DataPipes\DataPipe;
|
||||
|
|
@ -26,61 +22,21 @@ class GetSeasonEp implements DataPipe
|
|||
$nfo = "{$pi['dirname']}/{$pi['filename']}.nfo";
|
||||
if (file_exists($nfo)) {
|
||||
$properties['epNfo'] = $nfo;
|
||||
|
||||
return $this->getEpFromNfo($properties, $nfo);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
protected function getEpFromNfo(Collection $properties, string $nfo): Collection
|
||||
{
|
||||
$xml = simplexml_load_file($nfo);
|
||||
$type = $xml->getName();
|
||||
|
||||
if ($type === 'episodedetails') {
|
||||
return $this->getEpFromNfo($properties, $xml);
|
||||
} else if ($type === 'movie') {
|
||||
return $this->getFilmFromNfo($properties, $xml);
|
||||
if ($xml->season) {
|
||||
$properties['season'] = (int) $xml->season;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getEpFromFfprobe($properties);
|
||||
}
|
||||
|
||||
protected function getFilmFromNfo(Collection $properties, SimpleXMLElement $xml): Collection
|
||||
{
|
||||
$properties['type'] = Type::Movie;
|
||||
$properties['showTitle'] = $this->getProp($xml, 'title');
|
||||
$properties['movieTmdb'] = $this->getProp($xml, 'tmdbid');
|
||||
$properties['movieImdb'] = $this->getProp($xml, 'imdbid');
|
||||
if (!is_null($year = $this->getProp($xml, 'year'))) {
|
||||
$properties['movieYear'] = (int) $year;
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
protected function getEpFromNfo(Collection $properties, SimpleXMLElement $xml): Collection
|
||||
{
|
||||
if (!is_null($seas = $this->getProp($xml, 'season'))) {
|
||||
$properties['season'] = (int) $seas;
|
||||
}
|
||||
if (!is_null($ep = $this->getProp($xml, 'episode'))) {
|
||||
$properties['episode'] = (int) $ep;
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
protected function getProp(SimpleXMLElement $xml, string $prop): ?string
|
||||
{
|
||||
return $xml->$prop ? (string) $xml->$prop : null;
|
||||
}
|
||||
|
||||
protected function getEpFromFfprobe(Collection $properties): Collection
|
||||
{
|
||||
$ffmpeg = FFMpeg::create();
|
||||
$ffprobe = $ffmpeg->getFFProbe();
|
||||
$tags = $ffprobe->format($properties->get('path'))->get('tags');
|
||||
|
||||
if ($season = $tags['season_number'] ?? $tags['SEASON_NUMBER'] ?? null) {
|
||||
$properties['season'] = (int) $season;
|
||||
}
|
||||
|
||||
if ($episode = $tags['episode_sort'] ?? $tags['EPISODE_SORT'] ?? null) {
|
||||
$properties['episode'] = (int) $episode;
|
||||
if ($xml->episode) {
|
||||
$properties['episode'] = (int) $xml->episode;
|
||||
}
|
||||
|
||||
return $properties;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\DataPipes;
|
||||
|
||||
use App\Data\Enums\Type;
|
||||
use Illuminate\Support\Collection;
|
||||
use SimpleXMLElement;
|
||||
use Spatie\LaravelData\DataPipes\DataPipe;
|
||||
|
|
@ -31,7 +28,6 @@ class GetShowTmdb implements DataPipe
|
|||
}
|
||||
|
||||
$properties['showNfo'] = $nfo;
|
||||
$properties['type'] = Type::Episode;
|
||||
|
||||
$xml = simplexml_load_file($nfo);
|
||||
$properties['showTmdb'] = $this->getProp($xml, 'tmdbid');
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\DataPipes;
|
||||
|
||||
use App\Data\Enums\Type;
|
||||
use App\Data\FileData;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
|
@ -21,19 +18,12 @@ class ParseWatchFile implements DataPipe
|
|||
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
|
||||
{
|
||||
$properties['watched'] = Carbon::now();
|
||||
$properties['output'] = match ($properties['type']) {
|
||||
Type::Movie => sprintf(
|
||||
'%s-%d.json',
|
||||
Str::slug($properties['showTitle'] ?? uniqid()),
|
||||
$properties['movieYear'],
|
||||
),
|
||||
default => sprintf(
|
||||
$properties['output'] = sprintf(
|
||||
'%s-%dx%02d.json',
|
||||
Str::slug($properties['showTitle'] ?? uniqid()),
|
||||
Str::slug($properties['showTitle']),
|
||||
$properties['season'],
|
||||
$properties['episode']
|
||||
),
|
||||
};
|
||||
);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Data\DataPipes;
|
||||
|
||||
use App\Data\FileData;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\LaravelData\DataPipes\DataPipe;
|
||||
use Spatie\LaravelData\Lazy;
|
||||
use Spatie\LaravelData\Optional;
|
||||
use Spatie\LaravelData\Support\DataClass;
|
||||
use Spatie\LaravelData\Support\DataConfig;
|
||||
use Spatie\LaravelData\Support\DataProperty;
|
||||
|
||||
class ReadExportFile implements DataPipe
|
||||
{
|
||||
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
|
||||
{
|
||||
if (!$properties->has('path')) {
|
||||
return $properties;
|
||||
}
|
||||
|
||||
$properties['rawData'] = json_decode(file_get_contents($properties->get('path')), true) ?? [];
|
||||
|
||||
return $properties;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Data\DataPipes;
|
||||
|
||||
use App\Data\FileData;
|
||||
use App\Data\WatchExport;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
use Spatie\LaravelData\DataPipes\DataPipe;
|
||||
use Spatie\LaravelData\Lazy;
|
||||
use Spatie\LaravelData\Optional;
|
||||
use Spatie\LaravelData\Support\DataClass;
|
||||
use Spatie\LaravelData\Support\DataConfig;
|
||||
use Spatie\LaravelData\Support\DataProperty;
|
||||
|
||||
class ReadExportFiles implements DataPipe
|
||||
{
|
||||
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
|
||||
{
|
||||
if (!$properties->has('files')) {
|
||||
$properties['files'] = new DataCollection(WatchExport::class, []);
|
||||
}
|
||||
|
||||
if (!$properties->get('files')->count()) {
|
||||
return $properties;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
foreach ($properties->get('files') as $file)
|
||||
{
|
||||
$data[] = $file->rawData;
|
||||
}
|
||||
$properties['rawData'] = $data;
|
||||
|
||||
return $properties;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\DataPipes;
|
||||
|
||||
use App\Data\Enums\Type;
|
||||
use App\Data\FileData;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\LaravelData\DataPipes\DataPipe;
|
||||
use Spatie\LaravelData\Lazy;
|
||||
use Spatie\LaravelData\Optional;
|
||||
use Spatie\LaravelData\Support\DataClass;
|
||||
use Spatie\LaravelData\Support\DataConfig;
|
||||
use Spatie\LaravelData\Support\DataProperty;
|
||||
|
||||
class StructureData implements DataPipe
|
||||
{
|
||||
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
|
||||
{
|
||||
if (!$properties->has('rawData') || !count($properties->get('rawData'))) {
|
||||
return $properties;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$films = [];
|
||||
foreach ($properties->get('rawData') as $watch) {
|
||||
switch ($watch['type']) {
|
||||
case (Type::Episode->value):
|
||||
$data = $this->getEp($data, $watch);
|
||||
break;
|
||||
case (Type::Movie->value):
|
||||
$films[] = $this->getFilm($watch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$flat = [];
|
||||
foreach ($data as $show) {
|
||||
$show['seasons'] = array_values($show['seasons']);
|
||||
$flat[] = $show;
|
||||
}
|
||||
$properties['structuredData'] = ['shows' => $flat, 'movies' => $films];
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
protected function getEp(array $data, array $watch): array
|
||||
{
|
||||
$showTmdb = $watch['showTmdb'];
|
||||
$season = $watch['season'];
|
||||
$episode = $watch['episode'];
|
||||
$watched = $watch['watched'];
|
||||
if (!array_key_exists($showTmdb, $data)) {
|
||||
$data[$showTmdb] = [];
|
||||
$data[$showTmdb]['ids'] = ['tmdb' => $showTmdb];
|
||||
$data[$showTmdb]['seasons'] = [];
|
||||
}
|
||||
|
||||
if (!array_key_exists($season, $data[$showTmdb]['seasons'])) {
|
||||
$data[$showTmdb]['seasons'][$season] = [];
|
||||
$data[$showTmdb]['seasons'][$season]['number'] = $season;
|
||||
$data[$showTmdb]['seasons'][$season]['episodes'] = [];
|
||||
}
|
||||
|
||||
$data[$showTmdb]['seasons'][$season]['episodes'][] = [
|
||||
'number' => $episode,
|
||||
'watched_at' => $watched,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getFilm(array $watch): array
|
||||
{
|
||||
$filmTmdb = $watch['movieTmdb'];
|
||||
$filmYear = $watch['movieYear'];
|
||||
$title = $watch['showTitle'];
|
||||
$watched = $watch['watched'];
|
||||
|
||||
return [
|
||||
'watched_at' => $watched,
|
||||
'title' => $title,
|
||||
'year' => $filmYear,
|
||||
'ids' => [
|
||||
'tmdb' => $filmTmdb,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\Enums;
|
||||
|
||||
enum Type: string {
|
||||
case Unknown = "unknown";
|
||||
case Episode = "episode";
|
||||
case Movie = "movie";
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Exceptions\Quit;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
use Spatie\LaravelData\DataPipeline;
|
||||
|
||||
class WatchData extends Data
|
||||
{
|
||||
#[DataCollectionOf(WatchExport::class)]
|
||||
public DataCollection $files;
|
||||
public array $rawData = [];
|
||||
public array $structuredData = [];
|
||||
|
||||
public static function pipeline(): DataPipeline
|
||||
{
|
||||
return parent::pipeline()
|
||||
->through(DataPipes\ReadExportFiles::class)
|
||||
->through(DataPipes\StructureData::class)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use Spatie\LaravelData\DataPipeline;
|
||||
|
||||
class WatchExport extends FileData
|
||||
{
|
||||
public array $rawData = [];
|
||||
|
||||
public static function pipeline(): DataPipeline
|
||||
{
|
||||
return parent::pipeline()
|
||||
->through(DataPipes\ReadExportFile::class)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
|
|
@ -17,14 +15,10 @@ class WatchFile extends Data
|
|||
public ?string $showTmdb = null;
|
||||
public ?string $showImdb = null;
|
||||
public ?string $showTitle = null;
|
||||
public ?string $movieTmdb = null;
|
||||
public ?string $movieImdb = null;
|
||||
public int $movieYear = 1900;
|
||||
public ?string $epNfo = null;
|
||||
public int $season = 0;
|
||||
public int $episode = 0;
|
||||
public Carbon $watched;
|
||||
public Enums\Type $type = Enums\Type::Unknown;
|
||||
|
||||
public static function fromPath(string $path): static
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use App\Services\ProcessInput;
|
|||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
|
||||
use App\Queue\DatabaseConnector;
|
||||
use App\Services\Trakt;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -29,6 +28,5 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function register()
|
||||
{
|
||||
$this->app->instance(ProcessInput::class, new ProcessInput());
|
||||
$this->app->instance(Trakt::class, new Trakt());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Data\WatchData;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Trakt
|
||||
{
|
||||
protected $headers = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->headers = [
|
||||
'trakt-api-version' => 2,
|
||||
'trakt-api-key' => env('TRAKT_APP_ID'),
|
||||
'Authorization' => 'Bearer ' . env('TRAKT_OAUTH_TOKEN'),
|
||||
];
|
||||
}
|
||||
|
||||
public function request(array $headers = []): PendingRequest
|
||||
{
|
||||
return Http::withHeaders($this->headers + $headers)->baseUrl('https://api.trakt.tv');
|
||||
}
|
||||
|
||||
public function syncHistory(WatchData $data, OutputInterface $output = null): Response
|
||||
{
|
||||
$output?->writeln(sprintf('Submitting %s to trakt with headers %s', json_encode($data->structuredData), json_encode($this->headers)));
|
||||
|
||||
$resp = $this->request()->post('/sync/history', $data->structuredData);
|
||||
|
||||
$output?->writeln('Response (' . $resp->status() . '):' . PHP_EOL . json_encode($resp->json(), JSON_PRETTY_PRINT));
|
||||
|
||||
return $resp;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,14 +27,12 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.0",
|
||||
"danjones000/ffmpeg-mappable-media": "~0.2",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"illuminate/database": "^10.0",
|
||||
"illuminate/http": "^10.0",
|
||||
"illuminate/log": "^10.0",
|
||||
"illuminate/queue": "^10.0",
|
||||
"laravel-zero/framework": "^10.0",
|
||||
"illuminate/database": "^9.0",
|
||||
"illuminate/log": "^9.0",
|
||||
"illuminate/queue": "^9.2",
|
||||
"laravel-zero/framework": "^9.0",
|
||||
"nunomaduro/termwind": "^1.3",
|
||||
"php-ffmpeg/php-ffmpeg": "^1.0",
|
||||
"spatie/laravel-data": "^2.0"
|
||||
|
|
|
|||
3785
composer.lock
generated
3785
composer.lock
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue