Skip to content

Commit

Permalink
Add Stats Page (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tschucki authored Jan 1, 2025
1 parent 7460bcf commit 9ae6567
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 31 deletions.
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@

## Table Of Contents

* [About the Project](#about-the-project)
* [Features](#features)
* [Contributing](#contributing)
* [License](#license)
* [Authors](#authors)
* [Acknowledgements](#acknowledgements)
- [About the Project](#about-the-project)
- [Features](#features)
- [Contributing](#contributing)
- [License](#license)
- [Authors](#authors)
- [Acknowledgements](#acknowledgements)

## About The Project

Expand All @@ -35,17 +35,18 @@ videos into X264/AAC MP4 formats. The application also allows users to download
audio from videos, download files from URLs, defining the maximum file size, and more.

#### FFmpeg

The application uses FFmpeg to convert videos into the desired format and perform other operations on the videos.

## Features

- [X] Simple download of videos from various platforms and make them completely downloadable. (For private purposes)
- [X] Download target URLs
- [X] Convert videos to the desired format.
- [X] MP4 (H264/AAC)
- [X] Cut videos (From-to time)
- [X] Define maximum file size for videos by changing the bitrate
- [X] Collect statistics on converted videos
- [x] Simple download of videos from various platforms and make them completely downloadable. (For private purposes)
- [x] Download target URLs
- [x] Convert videos to the desired format.
- [x] MP4 (H264/AAC)
- [x] Cut videos (From-to time)
- [x] Define maximum file size for videos by changing the bitrate
- [x] Collect statistics on converted videos
- [ ] Take subtitles from YouTube
- [ ] Download only sound from videos.
- [ ] Presets definable (So that a normal user only has to click on "Convert" and done)
Expand All @@ -55,10 +56,10 @@ The application uses FFmpeg to convert videos into the desired format and perfor
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any
contributions you make are **greatly appreciated**.

* If you have suggestions for adding or removing projects, feel free
- If you have suggestions for adding or removing projects, feel free
to [open an issue](https://github.com/Tschucki/pr0verter/issues/new) to discuss it, or directly create a pull request.
* Create individual PR for each suggestion.
* Please also read through
- Create individual PR for each suggestion.
- Please also read through
the [Code Of Conduct](https://github.com/Tschucki/pr0verter/blob/main/.github/CODE_OF_CONDUCT.md) before
posting your first idea as well.

Expand All @@ -77,4 +78,4 @@ information.

## Authors

* **[Tschucki](https://github.com/Tschucki)** - *Maintainer*
- **[Tschucki](https://github.com/Tschucki)** - _Maintainer_
121 changes: 121 additions & 0 deletions app/Http/Controllers/StatController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace App\Http\Controllers;

use App\Enums\ConversionStatus;
use App\Models\Statistic;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Number;
use Inertia\Inertia;
use Throwable;

class StatController extends Controller
{
public function __invoke(Request $request)
{
return Inertia::render('Stats', [
'stats' => $this->getStats(),
]);
}

private function getStats()
{
$stats = [];

$allUrls = Statistic::whereNotNull('url')->get();
$urls = null;
try {
$urls = $allUrls->groupBy(static function ($item) {
return parse_url($item->url, PHP_URL_HOST);
});

$synonyms = [
[
'youtube.com',
'www.youtube.com',
'youtu.be',
'm.youtube.com',
],
];

foreach ($synonyms as $synonym) {
$mainDomain = $synonym[0];
$urls[$mainDomain] = $urls[$mainDomain] ?? collect();
foreach ($synonym as $domain) {
if ($domain !== $mainDomain) {
$urls[$mainDomain] = $urls[$mainDomain]->merge($urls[$domain]);
unset($urls[$domain]);
}
}
}

$urls = $urls->sortByDesc(fn ($item) => $item->count());
} catch (Throwable $th) {
Log::error('Could not group urls by domain', ['exception' => $th]);
}

$stats['favorite_url'] = [
'title' => 'Beliebteste Download-URL',
'value' => $urls ? $urls->keys()->first() : 'Keine URLs vorhanden',
];

$stats['currently_converting'] = [
'title' => 'Aktuell konvertierende Videos',
'value' => Statistic::whereIn('status', [ConversionStatus::PROCESSING, ConversionStatus::PREPARING, ConversionStatus::DOWNLOADING])->where('created_at', '>', now()->subHour())->count(),
];

$stats['uploaded_size'] = [
'title' => 'Traffic für hochgeladene Videos',
'value' => Number::fileSize(Statistic::sum('size'), 2),
];

$stats['extensions'] = [
'title' => 'Am häufigsten hochgeladene Dateiendung',
'value' => Statistic::select('extension')
->groupBy('extension')
->orderByRaw('COUNT(extension) DESC')
->first()->extension,
];

$stats['finished'] = [
'title' => 'Erfolgreiche Konvertierungen',
'value' => Statistic::where('status', ConversionStatus::FINISHED)->count(),
];

$stats['average_conversion_time'] = [
'title' => 'Durchschnittliche Konvertierungszeit',
'value' => Number::format(Statistic::where('status', ConversionStatus::FINISHED)->avg('conversion_time'), 2, locale: 'de-DE') . ' Sekunden',
];

$stats['favorite_time_to_convert'] = [
'title' => 'Beliebteste Konvertierungszeit',
'value' => Statistic::selectRaw('HOUR(created_at) as hour, COUNT(id) as count')
->groupBy('hour')
->orderByRaw('COUNT(id) DESC')
->first()->hour . ' Uhr',
];

$stats['added_watermarks'] = [
'title' => 'Wasserzeichen hinzugefügt',
'value' => Statistic::where('watermark', true)->where('status', ConversionStatus::FINISHED)->count() . ' Wasserzeichen',
];

$stats['auto_crop'] = [
'title' => 'Automatisch zugeschnittene Videos',
'value' => Statistic::where('auto_crop', true)->where('status', ConversionStatus::FINISHED)->count() . ' Videos',
];

$stats['trimmed'] = [
'title' => 'Videos zugeschnitten',
'value' => Statistic::whereNotNull('trim_start')->orWhereNotNull('trim_end')->count() . ' Videos',
];

$stats['removed_audio'] = [
'title' => 'Audio entfernt',
'value' => Statistic::where('audio', false)->count() . ' mal',
];

return $stats;
}
}
22 changes: 8 additions & 14 deletions resources/js/Layouts/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,29 +88,26 @@ const logout = async () => {
</Link>
</NavigationMenuItem>
<NavigationMenuItem class="block w-full">
<Link :href="route('conversions.list')" class="block w-full">
<Link :href="route('stats')" class="block w-full">
<NavigationMenuLink
:class="[
navigationMenuTriggerStyle(),
'!w-full !justify-center',
]">
Konvertierungen
Statistik
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem class="block w-full">
<a
target="_blank"
href="https://github.com/Tschucki/pr0verter/releases/latest"
class="block w-full">
<Link :href="route('conversions.list')" class="block w-full">
<NavigationMenuLink
:class="[
navigationMenuTriggerStyle(),
'!w-full !justify-center',
]">
Changelog
Konvertierungen
</NavigationMenuLink>
</a>
</Link>
</NavigationMenuItem>
<NavigationMenuItem class="block w-full">
<NavigationMenuLink
Expand Down Expand Up @@ -175,18 +172,15 @@ const logout = async () => {
</Link>
</NavigationMenuItem>
<NavigationMenuItem class="block w-full">
<a
target="_blank"
href="https://github.com/Tschucki/pr0verter/releases/latest"
class="block w-full">
<Link :href="route('stats')" class="block w-full">
<NavigationMenuLink
:class="[
navigationMenuTriggerStyle(),
'!w-full !justify-start',
]">
Changelog
Statistik
</NavigationMenuLink>
</a>
</Link>
</NavigationMenuItem>
<NavigationMenuItem class="block w-full">
<NavigationMenuLink
Expand Down
56 changes: 56 additions & 0 deletions resources/js/Pages/Stats.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup>
import { Head } from '@inertiajs/vue3';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card/index.js';
defineProps({
stats: {
type: Array,
required: true,
},
});
</script>

<template>
<Head>
<title>Pr0verter - Statistik</title>
<meta
name="description"
content="Der pr0verter ist ein Converter für das pr0gramm. Hier kannst Videos konvertieren." />
</Head>
<div class="grid w-full items-start gap-6">
<h1 class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
Statistik
</h1>

<div v-if="stats" class="my-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
<Card v-for="(stat, idx) in stats" :key="idx">
<CardHeader
class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
{{ stat.title }}
</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
{{ stat.value }}
</div>
</CardContent>
</Card>
</div>
<div class="text-muted-foreground">
<p>Folgende Domains fungieren als Synonyme für youtube.com:</p>
<ul>
<li>www.youtube.com</li>
<li>youtu.be</li>
<li>m.youtube.com</li>
</ul>
</div>
</div>
</template>

<style scoped></style>
3 changes: 3 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
use App\Http\Controllers\ListConverterController;
use App\Http\Controllers\PrivacyPolicyController;
use App\Http\Controllers\StartConverterController;
use App\Http\Controllers\StatController;
use Illuminate\Support\Facades\Route;

Route::get('/', HomeController::class)->name('home');

Route::get('/conversions', [ListConverterController::class, 'index'])
->name('conversions.list');

Route::get('/stats', StatController::class)->name('stats');

Route::post('/conversions', [ListConverterController::class, 'myConversions'])
->name('conversions.my');
Route::post('/converter/start', StartConverterController::class)
Expand Down

0 comments on commit 9ae6567

Please sign in to comment.