Commit 4d141fce authored by Guy Martin (Dwev)'s avatar Guy Martin (Dwev) 🇩🇰 Committed by Yassine Doghri
Browse files

feat: add transcript display to episode page

+ fix transcript parser

closes #411
parent 88851b02
Loading
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -128,6 +128,9 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
        $routes->get('chapters', 'EpisodeController::chapters/$1/$2', [
            'as' => 'episode-chapters',
        ]);
        $routes->get('transcript', 'EpisodeController::transcript/$1/$2', [
            'as' => 'episode-transcript',
        ]);
        $routes->options('comments', 'ActivityPubController::preflight');
        $routes->get('comments', 'EpisodeController::comments/$1/$2', [
            'as'                        => 'episode-comments',
@@ -205,6 +208,9 @@ $routes->get('/p/(:uuid)/activity', 'EpisodePreviewController::activity/$1', [
$routes->get('/p/(:uuid)/chapters', 'EpisodePreviewController::chapters/$1', [
    'as' => 'episode-preview-chapters',
]);
$routes->get('/p/(:uuid)/transcript', 'EpisodePreviewController::transcript/$1', [
    'as' => 'episode-preview-transcript',
]);

// Other pages
$routes->get('/credits', 'CreditsController', [
+67 −1
Original line number Diff line number Diff line
@@ -167,7 +167,7 @@ class EpisodeController extends BaseController
        return $cachedView;
    }

    public function chapters(): String
    public function chapters(): string
    {
        // Prevent analytics hit when authenticated
        if (! auth()->loggedIn()) {
@@ -228,6 +228,72 @@ class EpisodeController extends BaseController
        return $cachedView;
    }

    public function transcript(): string
    {
        // Prevent analytics hit when authenticated
        if (! auth()->loggedIn()) {
            $this->registerPodcastWebpageHit($this->episode->podcast_id);
        }

        $cacheName = implode(
            '_',
            array_filter([
                'page',
                "podcast#{$this->podcast->id}",
                "episode#{$this->episode->id}",
                'transcript',
                service('request')
                    ->getLocale(),
                is_unlocked($this->podcast->handle) ? 'unlocked' : null,
                auth()
                    ->loggedIn() ? 'authenticated' : null,
            ]),
        );

        if (! ($cachedView = cache($cacheName))) {
            // get transcript from json file
            $data = [
                'metatags' => get_episode_metatags($this->episode),
                'podcast'  => $this->podcast,
                'episode'  => $this->episode,
            ];

            if ($this->episode->transcript !== null) {
                $data['transcript'] = $this->episode->transcript;

                if ($this->episode->transcript->json_key !== null) {
                    /** @var FileManagerInterface $fileManager */
                    $fileManager = service('file_manager');
                    $transcriptJsonString = (string) $fileManager->getFileContents(
                        $this->episode->transcript->json_key
                    );

                    $data['captions'] = json_decode($transcriptJsonString, true);
                }
            }

            $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
                $this->podcast->id,
            );

            if (auth()->loggedIn()) {
                helper('form');

                return view('episode/transcript', $data);
            }

            // The page cache is set to a decade so it is deleted manually upon podcast update
            return view('episode/transcript', $data, [
                'cache' => $secondsToNextUnpublishedEpisode
                    ? $secondsToNextUnpublishedEpisode
                    : DECADE,
                'cache_name' => $cacheName,
            ]);
        }

        return $cachedView;
    }

    public function embed(string $theme = 'light-transparent'): string
    {
        header('Content-Security-Policy: frame-ancestors http://*:* https://*:*');
+29 −4
Original line number Diff line number Diff line
@@ -13,7 +13,6 @@ namespace App\Controllers;
use App\Entities\Episode;
use App\Models\EpisodeModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Modules\Media\FileManagers\FileManagerInterface;

class EpisodePreviewController extends BaseController
@@ -45,7 +44,7 @@ class EpisodePreviewController extends BaseController
        return $this->{$method}(...$params);
    }

    public function index(): RedirectResponse | string
    public function index(): string
    {
        helper('form');

@@ -55,7 +54,7 @@ class EpisodePreviewController extends BaseController
        ]);
    }

    public function activity(): RedirectResponse | string
    public function activity(): string
    {
        helper('form');

@@ -65,7 +64,7 @@ class EpisodePreviewController extends BaseController
        ]);
    }

    public function chapters(): RedirectResponse | string
    public function chapters(): string
    {
        $data = [
            'podcast' => $this->episode->podcast,
@@ -84,4 +83,30 @@ class EpisodePreviewController extends BaseController
        helper('form');
        return view('episode/preview-chapters', $data);
    }

    public function transcript(): string
    {
        // get transcript from json file
        $data = [
            'podcast' => $this->episode->podcast,
            'episode' => $this->episode,
        ];

        if ($this->episode->transcript !== null) {
            $data['transcript'] = $this->episode->transcript;

            if ($this->episode->transcript->json_key !== null) {
                /** @var FileManagerInterface $fileManager */
                $fileManager = service('file_manager');
                $transcriptJsonString = (string) $fileManager->getFileContents(
                    $this->episode->transcript->json_key
                );

                $data['captions'] = json_decode($transcriptJsonString, true);
            }
        }

        helper('form');
        return view('episode/preview-transcript', $data);
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ return [
    'comments' => 'Comments',
    'activity' => 'Activity',
    'chapters' => 'Chapters',
    'transcript' => 'Transcript',
    'description' => 'Episode description',
    'number_of_comments' => '{numberOfComments, plural,
        one {# comment}
@@ -44,4 +45,6 @@ return [
        'publish_edit' => 'Edit publication',
    ],
    'no_chapters' => 'No chapters are available for this episode.',
    'download_transcript' => 'Download transcript ({extension})',
    'no_transcript' => 'No transcript available for this episode.',
];
+30 −18
Original line number Diff line number Diff line
@@ -144,6 +144,7 @@ class TranscriptParser
                    break;

                case VTT_STATE_BLANK:
                    $speakercount = 0;
                    $state = VTT_STATE_TIME;
                    break;

@@ -154,20 +155,8 @@ class TranscriptParser

                case VTT_STATE_TEXT:
                    if (trim($line) === '') {
                        $sub = new stdClass();
                        $sub->number = $subNum;
                        [$startTime, $endTime] = explode(' --> ', $subTime);
                        $sub->startTime = $this->getSecondsFromVTTTimeString($startTime);
                        $sub->endTime = $this->getSecondsFromVTTTimeString($endTime);
                        $sub->text = trim($subText);
                        if ($subSpeaker !== '') {
                            $sub->speaker = trim((string) $subSpeaker);
                        }

                        $subText = '';
                        $state = VTT_STATE_TIME;
                        $subs[] = $sub;
                        ++$subNum;
                        //$subs[] = $sub;
                    } elseif ($subText !== '') {
                        $subText .= PHP_EOL . $line;
                    } else {
@@ -179,18 +168,36 @@ class TranscriptParser
                         * 2. Who is speaking
                         * 3. Any styling cues encoded in the VTT (which we dump)
                         * More information: https://www.w3.org/TR/webvtt1/
                         */
                        $vtt_speaker_pattern = '/^<.*>/';
                        $removethese = ['<', '>'];
                         *
                         * If there is more than one speaker in a cue, we also need
                         * to handle this, to repeat the start and end times for
                         * the second cue.
                         * */

                        $vtt_speaker_pattern = '/^<.*>/U';
                        $removethese = ['</v>', '<', '>'];
                        preg_match($vtt_speaker_pattern, $line, $matches);
                        if (isset($matches[0])) {
                            $subVoiceCue = explode(' ', str_replace($removethese, '', $matches[0]));
                            $subSpeaker = $subVoiceCue[1];
                            $subVoiceCue = str_replace($removethese, '', $matches[0]);
                            $subSpeaker = substr($subVoiceCue, strpos($subVoiceCue, ' '));
                        } else {
                            $subSpeaker = '';
                        }

                        $subText .= preg_replace($vtt_speaker_pattern, '', $line);
                        $sub = new stdClass();
                        $sub->number = $subNum;
                        [$startTime, $endTime] = explode(' --> ', $subTime);
                        $sub->startTime = $this->getSecondsFromVTTTimeString($startTime);
                        $sub->endTime = $this->getSecondsFromVTTTimeString($endTime);
                        $sub->text = trim($subText);
                        if ($subSpeaker !== '') {
                            $sub->speaker = trim($subSpeaker);
                        }

                        $subText = '';
                        $subs[] = $sub;
                        ++$subNum;
                    }

                    break;
@@ -215,6 +222,11 @@ class TranscriptParser
    private function getSecondsFromVTTTimeString(string $timeString): float
    {
        $timeString = explode('.', $timeString);
        if (substr_count($timeString[0], ':') === 1) {
            // add hours if only MM:SS.mmm format
            $timeString[0] = '00:' . $timeString[0];
        }

        return (strtotime($timeString[0]) - strtotime('TODAY')) + (float) "0.{$timeString[1]}";
    }
}
Loading