Skip to content

Commit

Permalink
add nicer UI. removed download functionality temporarily (todo)
Browse files Browse the repository at this point in the history
  • Loading branch information
nxrmqlly committed Oct 16, 2024
1 parent 9930d46 commit b211653
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 90 deletions.
9 changes: 9 additions & 0 deletions backend/controllers/browser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package controllers

import (
"github.com/wailsapp/wails/v2/pkg/runtime"
)

func (a *App) OpenBrowser(url string) {
runtime.BrowserOpenURL(a.ctx, url)
}
17 changes: 4 additions & 13 deletions backend/controllers/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,11 @@ import (
"unicode"

"youtube-downloader-go/backend/models"
"youtube-downloader-go/backend/utils"

"github.com/wailsapp/wails/v2/pkg/runtime"
)

var youtubeRegex, _ = regexp.Compile(`^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(?:-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|live\/|v\/)?)([\w\-]+)(\S+)?$`)

// Check if the url is valid youtube video URL
func isValidYouTubeURL(url string) bool {
return youtubeRegex.MatchString(url)
}

func slugify(input string) string {
// Remove non-ASCII characters
cleaned := strings.Map(func(r rune) rune {
Expand Down Expand Up @@ -47,12 +41,9 @@ func slugify(input string) string {
func (a *App) DownloadVideo(opts models.DownloadOptions) (string, error) {
//* check if the url is valid

var url = opts.URL
_, ytdlpPath := utils.YtDlpSetup()

if !isValidYouTubeURL(url) {
err := fmt.Errorf("invalid url: %s", url)
return err.Error(), err
}
var url = opts.URL

pathToSave, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{Title: "Save File"})

Expand All @@ -78,7 +69,7 @@ func (a *App) DownloadVideo(opts models.DownloadOptions) (string, error) {
}

// Execute yt-dlp command
cmd := exec.Command("yt-dlp", cmdArgs...)
cmd := exec.Command(ytdlpPath, cmdArgs...)
output, err := cmd.CombinedOutput()
if err != nil {
err := fmt.Errorf("error executing yt-dlp: %v\nOutput: %s", err, string(output))
Expand Down
33 changes: 33 additions & 0 deletions backend/controllers/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package controllers

import (
"encoding/json"
"fmt"
"os/exec"

"youtube-downloader-go/backend/models"
"youtube-downloader-go/backend/utils"
)

func (a *App) GetVideoInfo(videoURL string) (models.VideoInfo, error) {
_, ytDlpPath := utils.YtDlpSetup()

cmd := exec.Command(ytDlpPath, "--dump-json", videoURL)

// Capture the output
output, err := cmd.CombinedOutput()
if err != nil {
return models.VideoInfo{Valid: false}, fmt.Errorf("1 error executing yt-dlp: %w", err)
}

// Parse the JSON output
var videoInfo models.VideoInfo
err = json.Unmarshal(output, &videoInfo)
if err != nil {
return models.VideoInfo{Valid: false}, fmt.Errorf("error parsing JSON: %w", err)
}

videoInfo.Valid = true

return videoInfo, nil
}
9 changes: 9 additions & 0 deletions backend/models/video_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package models

type VideoInfo struct {
Valid bool
Title string `json:"title"`
Uploader string `json:"uploader"`
Duration int `json:"duration"` // Duration in seconds
ViewCount int `json:"view_count"`
}
41 changes: 25 additions & 16 deletions backend/utils/get_ytdlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,64 @@ import (
"net/http"
"os"
"path/filepath"

"github.com/wader/goutubedl"
)

const YtDlpGitgubRelease = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"

// If yt-dlp isn't installed yet, download it locally.
func getYtDlp() {
func downloadYtDlpBinary() (string, error) {
folderPath := "./bin-deps"

// Check if the folder exists
_, err := os.Stat(folderPath)

// If the folder doesn't exist, create it
_, err := os.Stat(folderPath)
if os.IsNotExist(err) {
err := os.Mkdir(folderPath, 0755)
if err != nil {
fmt.Println("Error creating folder:", err)
return
return "", err
}
}
out, err := os.Create("./bin-deps/yt-dlp.exe")

// create a new file in the folder

cwd, err := os.Getwd()
if err != nil {
panic(err)
}
pathToCreate := filepath.Join(cwd, "bin-deps", "yt-dlp.exe")

out, err := os.Create(pathToCreate)
if err != nil {
panic(err)
}
defer out.Close()

resp, err := http.Get(YtDlpGitgubRelease)
if err != nil {
panic(err)
return "", err
}
defer resp.Body.Close()

io.Copy(out, resp.Body)

return pathToCreate, nil
}

// Get yt-dlp if not exists, returns cwd and path of yt-dlp
func YtDlSetup() (string, string) {
// check if ./bin-deps/yt-dlp.exe exists
if _, err := os.Stat("./bin-deps/yt-dlp.exe"); os.IsNotExist(err) {
getYtDlp()
}
func YtDlpSetup() (string, string) {
var ytDlpPath string

var cwd, err = os.Getwd()
if err != nil {
panic(err)
}
goutubedl.Path = filepath.Join(cwd, "bin-deps", "yt-dlp.exe")

return cwd, goutubedl.Path
// check if ./bin-deps/yt-dlp.exe exists
if _, err := os.Stat("./bin-deps/yt-dlp.exe"); os.IsNotExist(err) {
ytDlpPath, _ = downloadYtDlpBinary()
} else {
ytDlpPath = filepath.Join(cwd, "bin-deps", "yt-dlp.exe")

}
return cwd, ytDlpPath
}
5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"svelte": "^3.49.0",
"vite": "^3.0.7"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0"
}
}
}
180 changes: 148 additions & 32 deletions frontend/src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,42 +1,158 @@
<script>
import { DownloadVideo } from '../wailsjs/go/controllers/App.js'
let url
let text = ""
/*
type DownloadOptions struct {
Path string
DownloadType string // "video" or "audio"
FileType string // e.g., "mp4", "mp3"
Resolution string // e.g., "720p", "1080p"
Quality string // e.g., "best", "worst"
}
*/
const downloadOptions = {
URL: url,
DownloadType: "video",
FileType: "mp4",
Resolution: "720p",
Quality: "best",
}
function handleButtonClick() {
DownloadVideo(downloadOptions).then((result) => {
text = result;
});
}
import { OpenBrowser, GetVideoInfo } from "../wailsjs/go/controllers/App.js";
import { onAccesibilityKeydown } from "./utils/accessibility.js";
import VideoModal from "./components/VideoModal.svelte";
import ThemeSwitcher from "./components/ThemeSwitcher.svelte";
let showModal = false;
let modalTitle;
let modalContent;
let url;
function openInBrowser(anchorUrl) {
OpenBrowser(anchorUrl);
}
function openModal() {
showModal = true;
}
function closeModal() {
showModal = false;
}
function handleButtonClick() {
if (!url) return;
GetVideoInfo(url)
.then((videoInfo) => {
console.log(videoInfo);
if (!videoInfo.Valid) {
modalTitle = "Error fetching video!";
modalContent = "Please check your URL and try again.";
} else {
modalTitle = videoInfo.title;
modalContent = `Duration: ${videoInfo.duration}\nUploader: ${videoInfo.uploader}\nViews : ${videoInfo.view_count}`;
}
openModal();
})
.catch((error) => {
console.log(error);
modalTitle = "Error fetching video!";
modalContent = "Please check your URL and try again.";
openModal();
});
}
</script>


<main>

<div class="input-box" id="input">
<p>{text}</p>
<input autocomplete="off" bind:value={url} class="input" id="name" type="text"/>
<button class="btn" on:click={handleButtonClick}>Get</button>
</div>
<ThemeSwitcher />

<h1 class="title"><span>Youtube Downloader</span></h1>

<div class="input-box" id="input">
<input autocomplete="off" bind:value={url} class="input" id="name" type="text" placeholder="Enter your URL here..."/>
<button class="btn" on:click={handleButtonClick}>►</button>
</div>
<footer>
<p>Works with <span
on:keydown={onAccesibilityKeydown}
on:click={() => openInBrowser("https://github.com/yt-dlp/yt-dlp")}
>almost anything</span>. Blazingly fast, I guess.</p>
<p>Made by <span
on:keydown={onAccesibilityKeydown}
on:click={() => openInBrowser("https://github.com/nxrmqlly")}
>@nxrmqlly</span> with

<span
on:keydown={onAccesibilityKeydown}
on:click={() => openInBrowser("https://youtu.be/dQw4w9WgXcQ")}
>❤</span> using Go and Svelte</p>
</footer>

{#if showModal}
<VideoModal
title={modalTitle}
content={modalContent}
onClose={closeModal}
/>
{/if}

</main>


<style>
main {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100dvh;
background-color: var(--bg-color);
}
.title {
color: var(--fg-color);
}
.title span {
display: inline-block;
}
.title span::after{
content: '';
height: 0.1em;
background: var(--accent);
border-radius: 0.1em;
display: block
}
.input-box {
margin-top: 1em;
display: flex;
border: 0.1em solid var(--accent);
border-radius: 0.1em;
overflow: hidden;
}
.input {
border: none;
padding: 10px;
width: 30rem;
outline: none;
flex: 1;
}
.btn {
background-color: var(--accent);
color: white;
border: none;
padding: 10px 15px;
transition: background-color 0.3s;
}
.btn:hover {
cursor: pointer;
background-color: var(--duo-accent);
}
footer {
user-select: none;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
font-size: 12px;
color: #787878;
}
footer span {
color: var(--accent);
text-decoration: none;
}
footer span:hover {
cursor: pointer;
text-decoration: underline;
}
</style>
Loading

0 comments on commit b211653

Please sign in to comment.