# -*- coding: utf-8 -*-

# arteconcert.py
# This file is part of qarte-5
#    
# Author: Vincent Vande Vyvre <vincent.vandevyvre@oqapy.eu>
# Copyright: 2011-2025 Vincent Vande Vyvre
# Licence: GPL3
# Home page: https://launchpad.net/qarte
#
# arte Concert main class

# Need libqt5multimedia5-plugins

import os
import shutil
import json
import urllib.request
from urllib.error import URLError
from html.parser import HTMLParser
import html
import logging
lgg = logging.getLogger(__name__)

from datetime import datetime, timedelta
from threading import Thread
import shlex
import subprocess
from PyQt5.QtGui import QFont, QTextCharFormat
from PyQt5.QtCore import (QObject, QCoreApplication, pyqtSignal, QLocale, QSize,
                          QTimer)
from PyQt5.QtMultimedia import QMediaPlayer
import m3u8

from downloader import Loader
from gui.warning import WarningBox
from gui.copywarning import CopyWarning
from data import USER_AGENT
from gui.videoplayer import VideoPlayer
from tools import SubtitleConverter


class ArteConcert(QObject):
    """Define the arte Concert server handler.

    """
    parsingFinished = pyqtSignal()
    concertsCompleted = pyqtSignal()
    mergingFinished = pyqtSignal(str, str)
    def __init__(self, core):
        super().__init__()
        self.core = core
        self.ui = core.ui
        self.page = self.ui.live_pg
        self.viewer = self.ui.live_pg.viewer
        self.lang = core.cfg.get('lang')
        self.default_quality = core.cfg.get('live_quality')
        self.fatal_error = False
        self.cache = core.workspace['live_tmp']
        self.temp_dir = core.workspace['temp']
        self.concerts = []
        self.to_download = []
        self.categories = []
        self.video_items = {}
        self.tasks = {}
        self.is_loading = False
        self.is_merging = 0
        self.loading_aborted = False
        self.current_video = False
        lang = QLocale.system().name()
        self.locale = QLocale(lang)
        self.time_ = datetime(2018, 1, 1, 0, 0, 0)
        # The next line crash with some graphics driver
        # bug: https://bugs.launchpad.net/bugs/1813131
        if core.cfg.get('video_player'):
            self.player = VideoPlayer(self, self.page.player_wdg)
            self.player.positionChanged.connect(self.on_player_position_changed)
            self.page.start_btn.clicked.connect(self.preview_current_video)
            self.page.pause_btn.clicked.connect(self.pause_preview)
            self.page.stop_btn.clicked.connect(self.stop_preview)
            self.page.level_sld.valueChanged.connect(self.set_sound_level)
            self.page.player_wdg.wheelEvent = self.on_video_wheel_event
            self.audio_player = VideoPlayer(self, self.page.audio_wdg)

        self.concertsCompleted.connect(self.show_recents)
        self.page.filter_led.commandTriggered.connect(self.search_and_find)
        self.mergingFinished.connect(self.on_merging_finished)

    def config_parser(self):
        """Configure the main pages parser.

        """
        self.update_concerts()
        if not self.concerts:
            #TODO show warning dialog
            lgg.warning("!!!!!! No concerts !!")
            return

        self.page.configure(self.concerts["categories"])
        for button in self.page.category_buttons:
            button.triggered.connect(self.change_category)

        self.parsingFinished.emit()

    def update_concerts(self):
        fpath = self.core.workspace['concerts_%s' % self.lang]
        cnt = self.get_update()
        self.concerts = []
        if cnt:
            try:
                self.concerts =  json.loads(cnt)
                lgg.info('Concert list updated')
            except Exception as why:
                lgg.warning("Unable to read the concerts list")
                lgg.warning("Reason: %s" % why)

        if not self.concerts:
            lgg.info('Use old data')
            self.get_old_concerts()

        else:
            try:
                with open(fpath, 'wb') as outf:
                    outf.write(cnt.encode('utf8', 'ignore'))
            except Exception as why:
                lgg.warning("Unable to write the arte concerts list")
                lgg.warning("Reason: %s" % why)

    def get_update(self):
        url = 'http://www.oqapy.eu/htdocs/releases/concerts_%s.jsn' % self.lang
        req = urllib.request.Request(url, data=None,
                                     headers={"User-Agent": USER_AGENT})
        lgg.info('Get update ...')
        try:
            content = urllib.request.urlopen(req, timeout=5)
            return str(content.read().decode('utf-8', 'replace'))
        except Exception as why:
            lgg.info('urllib in update concert error: %s, %s' % (url, why))
            return False

    def get_old_concerts(self):
        self.concerts = {}
        fpath = self.core.workspace['concerts_%s' % self.lang]
        if os.path.isfile(fpath):
            try:
                with open(fpath, 'r') as objfile:
                    cnt = objfile.read()
                    self.concerts =  json.loads(cnt)
            except Exception as why:
                lgg.warning("Can't read concert_%s: %s" %(self.lang, why))

    def display_available_concerts(self):
        def build_items():
            cats = self.concerts["categories"]
            for key in cats:
                if isinstance(self.concerts[key], list):
                    self.video_items[key] = []

                elif isinstance(self.concerts[key], dict):
                    keys = self.concerts[key].keys()
                    self.video_items[key] = {}
                    for k in keys:
                        d = self.concerts[key][k]
                        if isinstance(d, list):
                            self.video_items[key][k] = []
                            for x in d:
                                self.video_items[key][k].append(ConcertItem(x, key))

                        elif isinstance(d, dict):
                            self.video_items[key][k] = ConcertItem(d, key)

                else:
                    lgg.info("Unknow Item: %s" % key)
            self.concertsCompleted.emit()
        Thread(target=build_items).start()

    def show_recents(self):
        self.page.set_active_category(0, True)

    def change_category(self, category):
        self.show_category(category)
        self.page.set_active_category(self.concerts["categories"].index(category))

    def show_category(self, name):
        lgg.info("show category: %s" % name)
        category = self.video_items[name]
        self.viewer.clear()
        keys = sorted(category.keys())
        for key in keys:
            if isinstance(category[key], list):
                th = "medias/playlist.png"
                title = key

            elif isinstance(category[key], ConcertItem):
                self.check_thumbnails(category[key])
                th = category[key].preview
                title = category[key].title

            link = "%s--%s" %(name, key)
            self.viewer.add_item(th, title, link=link)

        self.viewer.clean()

    def show_sublist(self, sublist, name):
        QCoreApplication.processEvents()
        self.viewer.clearSelection()
        self.viewer.clear()
        QCoreApplication.processEvents()
        for v in sublist:
            if isinstance(v, list):
                continue

            self.check_thumbnails(v)
            th = v.preview
            title = v.title
            link = "%s--%s" % (name, title)
            self.viewer.add_item(th, title, link=link)
            QCoreApplication.processEvents()

        self.page.enable_current_button()
        # Prevent a rending issue in QListWidget
        timer = QTimer.singleShot(200, self.viewer.select_first)
        QCoreApplication.processEvents()
            
    def concert_selection_changed(self, current):
        if self.viewer.currentItem() is None:
            return

        link = self.viewer.currentItem().link
        if link is None:
            self.search_by_id()
            return

        lgg.info("selected: %s" % link)
        path = link.split("--")
        if len(path) == 2:
            video = self.video_items[path[0]][path[1]]
            if isinstance(video, list):
                lgg.info("Show sublist of %s items" % len(video))
                self.show_sublist(video, link)

            elif isinstance(video, ConcertItem):
                lgg.info("Video %s, %s selected" % (video.title, video.order))
                self.display_concert_infos(video)

        elif len(path) == 3:
            for video in self.video_items[path[0]][path[1]]:
                if video.title == path[2]:
                    break
            lgg.info("Video %s, %s selected" % (video.title, video.order))
            self.display_concert_infos(video)

        self.current_video = video

    def check_thumbnails(self, item):
        if item.preview is not None:
            if os.path.isfile(item.preview):
                return

        try:
            ext = os.path.splitext(item.imgurl)[1] or ".jpg"
        except AttributeError:
            item.preview = "medias/noPreview.png"
            return

        dest = os.path.join(self.cache, "%s%s" %(item.order, ext))
        if not os.path.isfile(dest):
            item.preview = self.fetch_thumbnail(dest, item.imgurl)

        else:
            item.preview = dest

    def fetch_thumbnail(self, fname, url):
        if url:
            for i in range(2):
                try:
                    with open(fname, 'wb') as objfile:
                        f = urllib.request.urlopen(url)
                        objfile.write(f.read())
                    return fname
                except Exception as why:
                    lgg.info("Can't load image: %s" % url)
                    lgg.info("Reason: %s" % why)
                    # API error code 104 'Connection reset by peer'
                    if not 'Errno 104' in why:
                        break

        return "medias/noPreview.png"

    def display_concert_infos(self, concert, updated=False):
        lgg.info("Display info of %s" % concert.title)
        self.page.show_preview(concert.preview)
        self.page.file_name_led.setText(concert.title)
        self.show_pitch(concert)
        QCoreApplication.processEvents()
        if not updated:
            self.search_available_videos(concert)

    def search_available_videos(self, item):
        """Return the available versions as a dict.

        Args:
        item -- ConcertItem instance
        """
        # Quick method first
        idx = item.order
        url = "".join(["https://api.arte.tv/api/",
                      "player/v2/config/{0}/{1}"])
        url = url.format(self.lang, idx)
        item.jsonurl = url
        content = self.get_page(url)
        if not content:
            # Failed, try second method
            content = self.get_page(item.url)
            p = Parser()
            p.feed(content)
            url = p.link
            if not url:
                lgg.info("json URL not found")
                return

            content = self.get_page(url)

        if not content:
            lgg.info("json URL unreadable")
            return

        try:
            dct = json.loads(content)
        except Exception as why:
            lgg.info("json read error: %s" % why)
            return

        # Debugging only
        #with open("item.jsn", "wb") as objf:
            #jsn = json.dumps(dct, sort_keys=True, indent=4, 
                         #separators=(',', ': '), ensure_ascii=False)
            #objf.write(jsn.encode('utf-8', 'replace'))

        data = dct["data"]["attributes"]["metadata"]
        desc = data["description"]
        title = item.title
        date = "%s %s" %(item.format_date(), item.format_duration())
        self.update_pitch(title, date, desc)
        if dct["data"]["attributes"]["streams"]:
            vers = dct["data"]["attributes"]["streams"][0]
            qualities, error = self.load_qualities(vers["url"])
        else:
            error = "Streams list empty !"

        if not error is None:
            lgg.info("Can't found streams, reason: %s" % error)
            self.show_available_qualities("Sorry, video not available")
            return

        item.qualities = qualities
        self.show_available_qualities(item)
        self.display_concert_infos(item, updated=True)

    def load_qualities(self, url):
        """load the video's streams.

        Args:
        version -- dict(streams)
        """
        lgg.info("Get streams for %s" % url)
        try:
            data = m3u8.load(url)
            qualities = []
            count = 0

            for playlist in data.playlists:
                qual = {}
                qual["id"] = count
                qual["resolution"] = playlist.stream_info.resolution
                qual["bandwidth"] = playlist.stream_info.bandwidth
                qual["uri"] = playlist.uri
                qual["audio"] = playlist.media[0].uri
                qual["base_url"] = data.base_uri
                qualities.append(qual)
                count += 1

            return qualities, None
        except Exception as why:
            lgg.info("Can't read the stream urls: %s" % why)
            reason = "Can't read the stream urls: %s" % why

        return False, reason

    def get_page(self, url):
        lgg.info('Load page: %s' % url)
        user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:49.0)'\
                  ' Gecko/20100101 Firefox/49.0'
        req = urllib.request.Request(url, data=None, 
                                 headers={"User-Agent": user_agent})
        try:
            content = urllib.request.urlopen(req)
        except Exception as why:
            lgg.warning('urllib error: %s, %s' % (url, why))
            return False

        return str(content.read().decode('utf-8', 'replace'))

    def show_pitch(self, item):
        """Called when the current item in the viewer has changed.

        """
        title = item.title
        date = "%s %s" %(item.format_date(), item.format_duration())
        pitch = "No description"
        if item.short_desc != "Unknow" and item.short_desc is not None:
            pitch = item.short_desc
            if item.long_desc != "Unknow" and item.long_desc is not None:
                pitch = pitch + "<br /><br />" + item.long_desc

        elif item.desc != "Unknow" and item.desc is not None:
            pitch = item.desc

        self.update_pitch(title, date, pitch, html=True)
 
    def update_pitch(self, *args, html=False):
        """Show the pitch of the current item.

        """
        self.page.editor_live.clear()
        font = QFont("sans", 10)
        c_form = QTextCharFormat()
        c_form.setFont(font)
        font1 = QFont("sans", 12)
        font1.setWeight(75)
        c_form1 = QTextCharFormat()
        c_form1.setFont(font1)
        self.page.editor_live.setCurrentCharFormat(c_form1)
        self.page.editor_live.appendPlainText(args[0])
        self.page.editor_live.setCurrentCharFormat(c_form) 
        self.page.editor_live.appendPlainText(args[1])
        self.page.editor_live.appendPlainText("")
        if html:
            self.page.editor_live.appendHtml(args[2])

        else:
            self.page.editor_live.appendPlainText(args[2])

        self.page.editor_live.verticalScrollBar().setValue(0)

    def show_available_qualities(self, video):
        self.page.vers_twg.clear()
        self.page.vers_twg.setRowCount(0)
        if isinstance(video, str):
            self.page.vers_twg.show_warning(video)

        else:
            for q in video.qualities:
                self.page.vers_twg.add_item(q)

    def select_video_to_download(self, quality, basket=True):
        """Select a video for the downloading basket.

        Args:
        idx -- index of video if called by a double click on a item
        """
        video = self.current_video
        if video in self.to_download:
            return

        lgg.info("Append item {0} to download basket".format(video.order))
        video.set_file_name(self.page.file_name_led.text())
        #video.set_quality(quality)
        video.quality = video.qualities[quality]
        if basket:
            self.to_download.append(video)
            self.page.live_basket.add_concert(video, self.page.pixmap)
            self.page.dl_live_wdg.dwnld_btn.setEnabled(True)
            self.ui.actionDo_wnload.setEnabled(True)

    def remove_item_from_basket(self, idx=None):
        """Called if user remove an item from downloading basket or when
        a downloading is complete.

        idx -- index of item
        """
        logger.info('Remove item %s frombasket' % idx)
        if idx == 'first':
            idx = 0

        elif idx is None:
            idx = self.get_current_item_index()

        self.page.live_basket.delete_item(idx)
        self.to_download.pop(idx)

    def get_stream_urls(self, item):
        content = self.get_page(item.jsonurl)
        if not content:
            lgg.info("Can't get the stream urls page: %s" % self.urlliberror)
            warn = WarningBox(5, self.urlliberror, self.ui)
            rep = warn.exec_()
            return

        try:
            dct = json.loads(content)
            strs = dct["videoJsonPlayer"]['VSR']
        except Exception as why:
            lgg.info("Can't read the stream urls: %s" % why)
            warn = WarningBox(5, why, self.ui)
            rep = warn.exec_()
            return

        if not strs:
            why = "Can't read the stream urls"
            try:
                msg = dct["videoJsonPlayer"]["custom_msg"]
                if msg["msg"].startswith(("Désolé", "Leider ist")):
                    why = msg["msg"]
            except:
                pass
            warn = WarningBox(5, why, self.ui)
            rep = warn.exec_()
            return

        return strs

    def clean_qualities(self, dct):
        keys = list(dct.keys())
        for key in keys:
            if not 'HTTP' in key:
                dct.pop(key)

        return dct

    def search_and_find(self):
        key = self.page.filter_led.text().lower()
        if len(key) < 4:
            lgg.info("Keyword too small: %s letters" % len(key))
            return

        def search(videos):
            if isinstance(videos, ConcertItem):
                if key in videos.title.lower():
                    candidates.append(videos)
            else:
                for video in videos:
                    if isinstance(video, str):
                        search(self.video_items[category][video])

                    elif isinstance(video, list):
                        search(video)

                    elif isinstance(video, dict):
                        for _, value in videos.items():
                            search(value)

                    elif isinstance(video, ConcertItem):
                        if key in video.title.lower():
                            candidates.append(video)

        candidates = []

        for category in self.video_items:
            search(self.video_items[category])

        if candidates:
            self.page.result_lbl.setText("Found %s concerts" % len(candidates))
            self.viewer.clear()
            for candidate in candidates:
                self.check_thumbnails(candidate)
                th = candidate.preview
                self.viewer.add_item(th, candidate.title, idx=candidate.order)

        else:
            self.page.result_lbl.setText("Not found")

        timer = QTimer.singleShot(5000, self.erase_result)

    def erase_result(self):
        self.page.result_lbl.setText("")

    def search_by_id(self):
        index = self.viewer.currentItem().index
        if index is None:
            return

        def search(videos):
            if isinstance(videos, ConcertItem):
                if index == videos.order:
                    self.item = videos
                    return

            else:
                for video in videos:
                    if isinstance(video, str):
                        search(self.video_items[category][video])
                        if self.item:
                            break

                    elif isinstance(video, list):
                        search(video)

                    elif isinstance(video, ConcertItem):
                        if video.order == index:
                            self.item = video
                            break

        self.item = False
        for category in self.video_items:
            search(self.video_items[category])
            if self.item:
                break

        if self.item:
            self.display_concert_infos(self.item)
            self.current_video = self.item

        else:
            lgg.warning("Unable to find the item %s" % index)

    def on_empty_list(self):
        self.page.dl_live_wdg.dwnld_btn.setEnabled(False)
        self.ui.actionDo_wnload.setEnabled(False)
        self.ui.actionCa_ncel.setEnabled(False)

    def edit_file_name(self, text=None):
        if text is None:
            text = self.page.file_name_led.text()

        lgg.info("Change file name: %s" % text)
        if text:
            self.current_video.set_file_name(text)

            # Refresh basket if needed
            if self.current_video in self.to_download:
                idx = len(self.to_download) - 1
                self.page.live_basket.rename(idx, text)

    def set_preferred_quality(self, quality):
        self.core.cfg.update('live_quality', quality)

    def start_download(self):
        if not self.to_download:
            self.page.dl_live_wdg.clear(True)
            self.page.dl_live_wdg.progress_lbl.setText("Merging last video ...")
            return

        video = self.to_download.pop(0)
        video.set_urls()
        index = video.order
        self.tasks[index] = video
        self.files_needed = []
        self.loaders = []
        target = self.get_video_target(index)
        self.files_needed.append([video.video_url, target])
        if video.audio_url is not None:
            target = self.get_audio_target(index)
            self.files_needed.append([video.audio_url, target])

        if video.subtitle_url is not None:
            target = self.get_subtitle_target(index)
            self.files_needed.append([video.subtitle_url, target])

        if len(self.files_needed) == 1:
            self.process_one_file(video)

        elif len(self.files_needed) == 2:
            self.process_two_files(video)

        elif len(self.files_needed) == 3:
            self.process_three_files(video)

        self.copy_summary(video)
        self.copy_thumbnail(video)

    def process_one_file(self, item):
        loader = Loader()
        loader.loadingProgress.connect(self.display_progress)
        loader.loadingFinished.connect(self.on_downloading_finished)
        if not self.initiate_loader(loader, self.files_needed[0][0], 
                                    self.files_needed[0][1]):
            return

        self.update_widgets_on_loading()
        lgg.info('Start download %s file size: %s'
                    %(item.title, self.file_size))
        lgg.info('URL: %s' % self.files_needed[0][0])
        self.is_loading = True
        self.loaders.append(loader)
        Thread(target=loader.download).start()

    def process_two_files(self, item):
        self.process_one_file(item)
        loader = Loader()
        if not self.initiate_loader(loader, self.files_needed[1][0], 
                                    self.files_needed[1][1]):
            return

        self.loaders.append(loader)
        lgg.info('Start download audio file size: %s' % self.file_size)
        Thread(target=loader.download).start()

    def process_three_files(self, item):
        self.process_two_files(item)
        loader = Loader()
        loader.loadingFinished.connect(self.on_subtitle_loaded)
        if not self.initiate_loader(loader, self.files_needed[2][0], 
                                    self.files_needed[2][1]):
            return

        self.loaders.append(loader)
        lgg.info('Start download subtitle file size: %s' % self.file_size)
        Thread(target=loader.download).start()

    def get_video_target(self, fname):
        return os.path.join(self.temp_dir, fname+"video.mp4")

    def get_audio_target(self, fname):
        return os.path.join(self.temp_dir, fname+"audio.mp4")

    def get_subtitle_target(self, fname):
        return os.path.join(self.temp_dir, fname+"subtitle.mp4")

    def get_stream(self, item):
        return item.qualities[item.quality]["url"]

    def initiate_loader(self, engine, url, fname):
        self.file_size = engine.config_loader(url, fname)
        if isinstance(self.file_size, int):
            return True

        lgg.info('Loading error: %s' % self.file_size)
        return False

    def update_widgets_on_loading(self):
        self.page.live_basket.disable_settings()
        self.page.dl_live_wdg.on_progress()
        self.ui.actionDo_wnload.setEnabled(False)
        self.ui.actionCa_ncel.setEnabled(True)

    def reset_loading_widgets(self):
        self.page.dl_live_wdg.dwnld_btn.setEnabled(True)
        self.page.dl_live_wdg.cancel_btn.setEnabled(False)
        self.ui.actionDo_wnload.setEnabled(True)
        self.ui.actionCa_ncel.setEnabled(False)

    def cancel_download(self):
        lgg.info('Cancel download')
        self.loading_aborted = True
        self.loader.kill_request = True

    def display_progress(self, percent, speed, remain):
        """Show the downloading progress informations.

        Args:
        percent -- percent downloaded
        speed -- bandwidth in Ko/sec.
        remain -- estimated remaining time
        """
        rtime = int(remain / speed)
        speed = self.locale.toString(int(speed / 1024))
        self.page.dl_live_wdg.progressBar.setValue(percent)
        self.page.dl_live_wdg.progress_lbl.setText(
                                "Speed: %s Kio/sec." % speed)
        m, s = divmod(rtime, 60)
        self.page.dl_live_wdg.progress_lbl2.setText(
                                "Remaining time: %s min. %s sec." %(m, s))

    def on_downloading_finished(self, fname):
        """Rename the video downloaded.

        Args:
        fname -- temporary file name
        """
        lgg.info('File %s downloaded' % fname)
        self.is_loading = False
        item = self.page.live_basket.remove_item(0)
        self.page.dl_live_wdg.clear()
        self.ui.actionCa_ncel.setEnabled(False)
        if not fname:
            self.handle_downloading_error()
            return

        file_path = self.get_file_path(fname)
        if len(self.files_needed) == 1:
            self.finalise(fname, file_path)

        elif len(self.files_needed) == 2:
            self.merge_audio_video(fname, file_path)

        elif len(self.files_needed) == 3:
            self.merge_audio_video_subtitle(fname, file_path)

        self.start_download()

    def on_subtitle_loaded(self, fname):
        converter = SubtitleConverter(fname)
        outf = fname.replace(".vtt", ".srt")
        converter.vtt_to_srt()
        self.files_needed[2][1] = outf

    def finalise(self, orig, dest):
        try:
            shutil.move(orig, dest)
            lgg.info('File renamed: %s' % os.path.basename(dest))
        except OSError as why:
            if why.errno == 22:
                lgg.warning("ERROR: %s" % why.strerror)
                self.call_filename_error(orig, dest)
        except Exception as why:
            lgg.warning("Can't rename %s" % dest)
            lgg.warning("Reason: %s" % why)

        if not self.to_download:
            #self.tasks = {}
            self.page.dl_live_wdg.progress_lbl.setText("All tasks finished")

    def get_file_path(self, orig):
        fld = self.core.cfg.get("music_folder")
        idx = os.path.split(orig)[1].rstrip("video.mp4")
        title = self.tasks[idx].fname
        return os.path.join(fld, title+".mp4")

    def merge_audio_video(self, fvideo, dest):
        audio = self.files_needed[1][1]
        outf = fvideo.replace("video", "final")
        cmd = 'ffmpeg -y -i %s -i %s -c:v copy -c:a aac "%s"'\
                % (fvideo, audio, outf)
        self.is_merging += 1
        def merge(*args):
            cmd = shlex.split(args[0])
            r = subprocess.Popen(cmd, universal_newlines=True, 
                                 stdout=subprocess.PIPE, 
                                 stderr=subprocess.STDOUT).communicate()
            self.is_loading = False
            self.mergingFinished.emit(args[1], args[2])

        Thread(target=merge, args=(cmd, outf, dest)).start()

    def merge_audio_video_subtitle(self, fvideo, dest):
        audio = self.files_needed[1][1]
        subt = self.files_needed[2][1]
        outf = fvideo.replace("video", "final")
        dest = dest.replace("mp4", "mkv")
        cmd = 'ffmpeg -y -i "%s" -i "%s" -i "%s"' %(fvideo, audio, subt)
        cmd += " -map 0 -map 1 -metadata:s:1 language=mul -metadata:s:1"
        cmd += " handler='mul' -metadata:s:1 title='mul' -map 2 -metadata:s:2"
        cmd += " language=fra -metadata:s:2 handler='fra (VO)'" 
        cmd += " -metadata:s:2 title='fra (VO)'"
        cmd += " -disposition:s -default -disposition 0 -c:v copy -c:a copy -c:s copy"
        cmd += ' "%s"' % out
        self.is_merging += 1
        def merge(*args):
            cmd = shlex.split(args[0])
            r = subprocess.Popen(cmd, universal_newlines=True, 
                                 stdout=subprocess.PIPE, 
                                 stderr=subprocess.STDOUT).communicate()
            self.is_loading = False
            self.mergingFinished.emit(args[1], args[2])

        Thread(target=merge, args=(cmd, outf, dest)).start()

    def on_merging_finished(self, merged, dest):
        lgg.info("Merging finished, rename ...")
        self.finalise(merged, dest)
        self.is_merging -= 1

    def copy_summary(self, video):
        """Save the summary into a file.

        Args:
        video -- TVItem instance
        """
        fld = self.core.cfg.get("music_folder")
        fname = video.fname
        if not self.core.cfg.get('pitch_live'):
            return

        if self.core.cfg.get('pitch_live_unique'):
            path = os.path.join(fld, 'index')

        else:
            path = os.path.join(fld, os.path.splitext(fname)[0])

        title = video.title
        _, date, summary = self.get_description(video)
        txt = "\n* %s\n  %s\n\n  %s\n" %(title, date, summary)
        try:
            with open(path, 'a') as outf:
                outf.write(txt)
                lgg.info("Summary saved: %s" % path)
        except Exception as why:
            lgg.warning("Can't write summary: %s" % path)
            lgg.warning("Reason: %s" % why)

    def copy_thumbnail(self, video):
        """Save the thumbnail into a file.

        Args:
        video -- TVItem instance
        """
        if self.core.cfg.get('copythumb_live'):
            fld = self.core.cfg.get("music_folder")
            path = os.path.splitext(video.fname)[0] + ".jpg"
            path = os.path.join(fld, path)
            try:
                orig = video.preview
                shutil.copy(orig, path)
                lgg.info("Thumbnail saved")
            except:
                return

    def handle_downloading_error(self):
        """Display a warning box when the downloading failed.

        """
        url = self.loader.url
        error = self.loader.last_error
        lgg.warning("Downloading error: %s" % error)
        lgg.warning("URL: %s" % url)
        warn = WarningBox(8, (url, error), self.ui)
        rep = warn.exec_()
        if self.page.live_basket.lst_movies:
            self.start_download()

    def call_filename_error(self, orig, target):
        path, base = os.path.split(target)
        fname, ext = os.path.splitext(base)
        self.new_name = ""
        warn = CopyWarning(fname, self)
        rep = warn.exec_()
        if not self.new_name:
            self.new_name = fname
        dest = os.path.join(path, self.new_name + ext)
        self.finalise(orig, dest)

    def get_description(self, item):
        title = item.title
        date = "%s %s" %(item.format_date(), item.format_duration())
        pitch = "No description"
        if item.short_desc != "Unknow" and item.short_desc is not None:
            pitch = item.short_desc
            if item.long_desc != "Unknow" and item.long_desc is not None:
                pitch = pitch + "<br /><br />" + item.long_desc

        elif item.desc != "Unknow" and item.desc is not None:
            pitch = item.desc

        return title, date, pitch

    #---------------------------------------------------------
    # Previewing video
    #---------------------------------------------------------
    def preview_current_video(self):
        if self.player.is_on_pause:
            self.page.set_running_player()
            self.player.start()
            self.audio_player.start()
            return

        if not self.current_video:
            return

        for q in self.current_video.qualities:
            if q["resolution"][1] == 216:
                break

        base = q["base_url"]
        video_url = q["uri"]
        if not video_url.startswith("http"):
            video_url = base + q["uri"]

        audio_url = q["audio"]
        if not audio_url.startswith("http"):
            audio_url = base + q["uri"]

        self.create_preview(video_url, audio_url)

    def play_preview(self, video, audio):
        self.player.set_media(video)
        self.audio_player.set_media(audio)
        self.page.thumbView.hide()
        self.page.player_wdg.show()
        self.page.set_running_player()
        d = self.current_video.duration
        if d is not None:
            delta = timedelta(seconds=d)
            n = self.time_ + delta
            t = "%02d:%02d:%02d" %(n.hour, n.minute, n.second)
            self.page.duration_lbl.setText(t)
        QCoreApplication.processEvents()
        self.player.start()
        self.audio_player.start()

    def pause_preview(self):
        self.page.set_player_pause()
        self.player.set_on_pause()
        self.audio_player.set_on_pause()

    def stop_preview(self):
        self.player.finish()
        self.audio_player.finish()
        self.page.player_wdg.hide()
        self.page.thumbView.show()
        self.page.stop_player()

    def on_player_position_changed(self, position):
        d = timedelta(milliseconds=position)
        n = self.time_ + d
        t = "%02d:%02d:%02d" %(n.hour, n.minute, n.second)
        self.page.elapsed_lbl.setText(t)

    def set_sound_level(self, level):
        self.audio_player.set_sound_level(level)
        self.page.level_lbl.setText(str(level))

    def on_video_wheel_event(self, event):
        lgg.info("Wheel event on player: %s" % event.angleDelta().y())
        level = event.angleDelta().y() / 24
        self.audio_player.setVolume(level+self.page.level_sld.value())
        event.accept()

    def on_space_key_pressed(self):
        if self.player.is_running:
            if not self.player.is_on_pause:
                self.pause_preview()
                return True

        else:
            if self.player.is_on_pause:
                self.page.set_running_player()
                self.player.start()
                self.audio_player.start()
                return True

        return False

    def create_preview(self, video_m3u, audio_m3u):
        video_url = video_m3u.replace(".m3u8", ".mp4")
        audio_url = audio_m3u.replace(".m3u8", ".mp4")
        self.play_preview(video_url, audio_url)


class ConcertItem:
    def __init__(self, item, cat):
        """Item concert in arte Live Web.

        Args:
        cat -- category
        item -- values provided by the parser
        """
        self.category = cat
        self.order = item["programId"]
        self.title = item["title"]
        self.date = ""
        self.expires = ""
        self.desc = item["description"]
        self.short_desc = item["shortDescription"]
        self.long_desc = item["fullDescription"]
        self.duration = item["duration"]
        self.url = item["url"]
        self.imgurl = item["pict"]
        self.jsonurl = ""
        self.preview = None
        self.qualities = None
        self.quality = None
        self.video_url = None
        self.audio_url = None
        self.subtitle_url = None
        self.lang = ""
        self.fname = None
        self.credits = item["credits"]
        self.set_file_name()

    def set_file_name(self, new=False):
        if not new:
            new = self.title

        f = new.replace('/', '-').replace('»', '_').replace('«', '_')
        f = f.replace('\\', ' ').replace('.', ' ').replace(':', '')
        if len(f) > 48:
            f = "%s---%s" %(f[:35], f[-10:])

        self.fname = f

    def set_urls(self):
        if not self.quality["uri"].startswith("http"):
            self.video_url = self.find_stream(os.path.join(
                                        self.quality["base_url"], 
                                        self.quality["uri"]))

        else:
            self.video_url = self.find_stream(self.quality["uri"])

        try:
            if self.quality["audio"]:
                if not self.quality["audio"].startswith("http"):
                    self.audio_url = self.find_stream(os.path.join(
                                        self.quality["base_url"], 
                                        self.quality["audio"]))

                else:
                    self.audio_url = self.find_stream(self.quality["audio"])
        except KeyError:
            self.audio_url = None

        try:
            if self.quality["subtitle"]:
                if not self.quality["subtitle"].startswith("http"):
                    self.subtitle_url = self.get_vtt_url(os.path.join(
                                            self.quality["base_url"], 
                                            self.quality["subtitle"]))
                else:
                    self.subtitle_url = self.get_vtt_url(self.quality["subtitle"])
        except KeyError:
            self.subtitle_url = None

    def get_image(self):
        if self.preview is not None:
            try:
                img = urllib2.urlopen(self.pix).read()
            except Exception as why:
                return None

            else:
                return img

    def format_date(self):
        return self.date.replace('T', ' ').rstrip('Z')

    def format_duration(self):
        if self.duration is None:
            return ""

        min_, sec = divmod(int(self.duration), 60)
        if min_ < 60:
            return "%s min. %s sec." %(min_, sec)

        hours, min_ = divmod(min_, 60)
        return "%s h. %s min. %s sec." %(hours, min_, sec)

    def find_stream(self, url):
        lgg.info("read: %s" % url)
        try:
            data = m3u8.load(url)
            base = data.base_uri
            if not base.endswith("/"):
                base += "/"

            if type(data.segment_map) == dict:
                return base + data.segment_map["uri"]

            return base + data.segment_map[0].uri
        except Exception as why:
            lgg.info("Error when reading m3u8:\n\t%s" % why)
            return False

    def get_vtt_url(self, url):
        lgg.info("Get m3u8 file: \n   %s" % url)
        try:
            data = m3u8.load(url)
            base = data.base_uri
            if not base.endswith("/"):
                base += "/"

            return base + data.files[0]
        except Exception as why:
            lgg.info("Error when reading m3u8:\n\t%s" % why)
            return False


class _Parser(HTMLParser):
    """Define the parser of arte Concert's pages.

    """
    def __init__(self):
        super().__init__()
        self.link = False
        self.inframe = False

    def handle_starttag(self, tag, attrs):
        if tag == "iframe":
            self.inframe = True
            for att in attrs:
                if att[0] == 'src':
                    self.link = att[1]

    def handle_endtag(self, tag):
        if tag == "iframe":
            self.inframe = False

