#!/usr/bin/env python3
# -*- coding: utf-8 -*-

""" Author:                     Hendrik Schutter, localhorst@mosad.xyz
    Date of creation:           2022/02/13
    Date of last modification:  2022/01/26
"""

import os
import sys
import time
import subprocess
import datetime
from dataclasses import dataclass
from tqdm import tqdm

@dataclass
class MediaFile:
    name: str #without extension
    extension: str #without dot
    full_path: str
    codec: str
    size: int #bytes
    resolution: (int , int)
    duration: int #in sec

def supported_file_extension(filename):
    if filename.endswith('.mp4') or filename.endswith('.mkv'):
        return True
    return False

def get_length(filename):
    result = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
                             "format=duration", "-of",
                             "default=noprint_wrappers=1:nokey=1", filename],
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL)
    try:
        length = float(result.stdout)
    except ValueError:
        length = 0.0
    return length

def get_codec(filename):
    result = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0",
                             "-show_entries", "stream=codec_name",
                             "-of", "default=noprint_wrappers=1:nokey=1", filename],
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL)
    return str(result.stdout.decode("utf-8")).rstrip("\n")

def get_resolution(filename):
    result = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0",
                             "-show_entries", "stream=width,height",
                             "-of", "default=noprint_wrappers=1:nokey=1", filename],
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL)
    try:
        resolution = ((result.stdout.decode("utf-8").rstrip("\n").partition('\n')[0]), (result.stdout.decode("utf-8").rstrip("\n").partition('\n')[2]))
    except ValueError:
        resolution = (0, 0)
    return resolution

def get_number_of_files(path):

    #filter(supported_file_extension, files)

    return sum([len(list(filter(supported_file_extension, files))) for r, d, files in os.walk(path)])

def human_readable_size(size, decimal_places=2):
    for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']:
        if size < 1024.0 or unit == 'PiB':
            break
        size /= 1024.0
    return f"{size:.{decimal_places}f} {unit}"

def cut_file_name(filename, max_lenght, ellipsis="..."):
    if len(filename) > max_lenght:
        return filename[:max_lenght-len(ellipsis)] + ellipsis
    else:
        return filename

def scan_files(path):
    total_numbers_to_scan = get_number_of_files(path)

    media_files = list() #stores all found files with metadata

    pbar = tqdm(total=total_numbers_to_scan) #print progress bar
    
    for root, dirs, files in os.walk(path, topdown=True):
        for name in  filter(supported_file_extension, files):
            pbar.set_description("Processing %s" % str("{:<32}".format(cut_file_name(name, 32))))
            full_path = os.path.join(root, name)
            media_files.append( MediaFile(name=os.path.basename(name), extension=os.path.splitext(name)[1], full_path=full_path, codec=get_codec(full_path), size=os.path.getsize(full_path), resolution=get_resolution(full_path),  duration=get_length(full_path)))
            pbar.update(1)
    pbar.close()   
    return media_files

def print_all(media_files):
    for media_file in media_files:
        print ("{:<64} | {:<8} | {:<16} | {:<8} | {:<16}".format(cut_file_name(media_file.name, 64), str(datetime.timedelta(seconds=media_file.duration)).split(".")[0], human_readable_size(media_file.size), media_file.codec, str(media_file.resolution[0])+"x"+str(media_file.resolution[1])))

def print_codecs(media_files):
    codecs = list()

    for media_file in media_files:
        if next((codec_type for codec_type in codecs if codec_type["name"] == media_file.codec), False):
                #this codec type is already in list --> add occurrence
                for codec_type in codecs:
                    if (codec_type["name"] == media_file.codec):
                        codec_type["occurrence"] +=1
        else:
            #this const is NOT in list --> create occurrence dict and add to list
            thisdict = { }
            thisdict["name"] = media_file.codec
            thisdict["occurrence"] = 1
            codecs.append(thisdict)

    #sort bases on occurrence
    codecs.sort(key=lambda x: x["occurrence"], reverse=True)

    for codec_type in codecs:
        print ("{:<8} | {:<8} | {:<8}".format(codec_type["name"], str(codec_type["occurrence"])+" files", str(round(float(float(100 / len(media_files)) * codec_type["occurrence"]),1))+"%"))
    print("\ntotal "+ str(len(media_files)) + str(" files"))

def main() -> None:
    if(len(sys.argv) != 2):
        path = '.' #use current pwd
    else:
        path = sys.argv[1] #use arg0 as path

    media_files = scan_files(path) #scan all media files
    
    print("")
    print_all(media_files)

    print("")
    print_codecs(media_files)


if __name__ == "__main__":
    main()
