123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- #! /usr/bin/env python
- # -*- coding: utf8 -*-
- #stov - a program to subscribe to channels and users from youtube
- # and download the videos automatically
- #
- # written by Helmut Pozimski 2012-2013
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; version 2
- # of the License.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor,
- # Boston, MA 02110-1301, USA.
- from __future__ import unicode_literals
- import sys
- import gettext
- import os
- import sqlite3
- import email
- import smtplib
- import subprocess
- import signal
- from email.mime.multipart import MIMEMultipart
- from email.mime.text import MIMEText
- from optparse import OptionParser
- import subscription
- import youtube
- import configuration
- from outputhelper import printf
- """Determine the path where the stov files are for localization"""
- locale_path = sys.path[0] + "/locale"
- """Initialize gettext to support translation of the program"""
- try:
- trans = gettext.translation("stov", locale_path)
- except IOError:
- gettext.install("stov")
- printf(_("Translation files could not be found, localization "
- "won't be available"), outputlevel="default",
- descriptor="stderr")
- else:
- if sys.version_info >= (3, 0):
- trans.install()
- else:
- trans.install(unicode=True)
- """Define a handler for signals sent to the program"""
- def sighandler(signum, frame):
- if signum == 2:
- printf(_("STRG+C has been pressed, quitting..."),
- outputlevel="default", descriptor="stderr")
- elif signum == 15:
- printf(_("Received SIGTERM, quitting..."),
- outputlevel="default", descriptor="stderr")
- os.killpg(os.getpid(), 1)
- os.remove("/tmp/stov.lock")
- sys.exit(0)
- signal.signal(signal.SIGTERM, sighandler)
- signal.signal(signal.SIGINT, sighandler)
- """Overwrite the default OptionParser class so error messages
- can be localized
- """
- class MyOptionParser(OptionParser):
- def error(self, msg):
- if "invalid integer" in msg:
- printf(_("option %s requires an integer value")
- % msg.split()[1],
- outputlevel="default", descriptor="stderr")
- self.exit()
- elif "an argument" in msg:
- printf(_("option %s requires an argument") % msg.split()[0],
- outputlevel="default", descriptor="stderr")
- self.exit()
- elif "no such" in msg:
- printf(_("invalid option %s") % msg.split()[3],
- outputlevel="default", descriptor="stderr")
- self.exit()
- else:
- printf(msg, outputlevel="default", descriptor="stderr")
- self.exit()
- """Process the given options and parameters,
- add: Add a new subscription (which can be a search, channel or playlist)
- channel: with add, specify the name of the channel or user
- lssubs: List the currently available subscriptions
- remove: remove a subscription
- update: update the information about the available videos
- download: download all available videos which haven't already been downloaded
- search: optionally add a search string to a new subscription or create a
- search subscription with add
- playlist: with add, subscribe to a youtube playlist
- catchup: Mark all videos in a subscription as downloaded
- version: Print version number
- quiet: Suppress all output
- verbose: Print normal output + diagnostical messages
- clean-database: Clean the database of old entries, meaning videos that
- are no longer present in the current API response of youtube
- """
- parser = MyOptionParser(usage=_("Usage: %prog [options]"), prog="stov",
- add_help_option=True, conflict_handler="resolve")
- parser.add_option("-h", "--help", action="store_true", dest="help",
- help=_("show this help message and exit"))
- parser.add_option("-a", "--add", dest="add", action="store_true",
- help=_("Add a new subscription (requires either --search, \
- --channel or --playlist)"))
- parser.add_option("-p", "--playlist", dest="playlist",
- help=_("Add a new Playlist subscription (requires add)"))
- parser.add_option("-l", "--lssubs", action="store_true", dest="list",
- help=_("List the currently available subscriptions"))
- parser.add_option("-r", "--remove", type="int", dest="deleteid",
- help=_("remove a subscription"))
- parser.add_option("-u", "--update", action="store_true", dest="update",
- help=_("update the information about the available videos"))
- parser.add_option("-d", "--download", action="store_true", dest="download",
- help=_("download all available videos which haven't already been downloaded"))
- parser.add_option("-s", "--search", dest="searchparameter",
- help=_("optionally add a search string to a new channel subscription or \
- create a new search subscription (requires --add)"))
- parser.add_option("-l", "--lsvids", type="int", dest="subscriptionid",
- help=_("Print all videos from a subscription"))
- parser.add_option("-c", "--catchup", dest="catchup",
- help=_("Mark all videos from one channel as read \
- (requires subscription-id as argument)"))
- parser.add_option("-c", "--channel", dest="channel",
- help=_("specify a channel for a new subscription (requires --add)"))
- parser.add_option("-l", "--license", dest="license", action="store_true",
- help=_("show the license of the program"))
- parser.add_option("-v", "--version", dest="version", action="store_true",
- help=_("show the current running version number"))
- parser.add_option("-q", "--quiet", dest="quiet", action="store_true",
- help=_("Suppress all output"))
- parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
- help=_("Be verbose and print also diagnostical messages"))
- parser.add_option("-c", "--clean-database", dest="cleanup",
- action="store_true", help=_("Clean the database of entries no longer listed "
- "in the current API response"))
- (options, arguments) = parser.parse_args()
- """Check if stov is run directly from command line since it shouldn't be
- loaded as a module
- """
- if __name__ != "__main__":
- print >> sys.stderr, """This file should not be imported as a module
- please run it directly from command line"""
- sys.exit(1)
- """Variable to determine if the exit code should be success or not"""
- exit_status = True
- """Check which outputlevel is defined and save it to a temporary variable
- accordingly. Output generated before this will be printed to stdout regardless
- of the user defined setting
- """
- if options.verbose is True and options.quiet is True:
- printf(_("--quiet and --verbose can't be defined at the same time, "
- "exiting."), outputlevel="default")
- sys.exit(1)
- elif options.verbose is True:
- outputlevel = "verbose"
- elif options.quiet is True:
- outputlevel = "quiet"
- else:
- outputlevel = "default"
- """Create the lock file which is used to determine if another instance is
- already running by chance, the program shouldn't be run in this case since
- we want to prevent concurent access to the database.
- """
- if os.access("/tmp/stov.lock", os.F_OK):
- try:
- lockfile = open("/tmp/stov.lock", "r")
- except IOError:
- printf(_("Lock file could not be opened, please check that "
- "it exists and is readable, quitting now"),
- outputlevel="default", level=outputlevel, descriptor="stderr")
- sys.exit(1)
- oldpid = lockfile.read().strip()
- if os.access("/proc/" + oldpid, os.F_OK):
- printf(_("The lock file already exists, probably another"
- "instance of this program is already running\n"
- "if you are sure this is not the case, delete it"
- " manually and try again!"),
- outputlevel="default", level=outputlevel, descriptor="stderr")
- sys.exit(1)
- lockfile.close()
- if os.access("/proc/" + oldpid, os.F_OK) is not True:
- try:
- os.remove("/tmp/stov.lock")
- except os.error:
- printf(_("Old lock file could not be deleted!"),
- outputlevel="default", level=outputlevel, descriptor="stderr")
- try:
- lockfile = open("/tmp/stov.lock", "w")
- lockfile.write(str(os.getpid()))
- lockfile.close()
- except IOError:
- printf(_("Lock file could not be created, please check that /tmp is "
- "writable and properly configured, quitting now"),
- outputlevel="default", level=outputlevel, descriptor="stderr")
- sys.exit(1)
- """Check if the configuration directory exists and is writeable. If it \
- doesnt, create it using the configuration class.
- """
- if os.access(os.environ['HOME'] + "/.stov", os.F_OK & os.W_OK) is not True:
- printf(_("This seems to be the first time you run the programm, do you"
- " want to run the interactive assistant? (yes/no)"),
- outputlevel="default", level=outputlevel, descriptor="stdout")
- conf = configuration.conf()
- temp_input = raw_input()
- if temp_input == "yes":
- conf.assist()
- else:
- printf(_("Writing initial configuration according to default values"),
- outputlevel="default", level=outputlevel, descriptor="stdout")
- conf.Initialize()
- else:
- conf = configuration.conf()
- if conf.CheckConfig() is not True:
- printf(_("Your configuration needs to be updated, performing"
- " update now."), outputlevel="default", level=outputlevel,
- descriptor="stdout")
- conf.UpdateConfig()
- if conf.CheckDB() is not True:
- printf(_("Your database needs to be updated, performing"
- " update now."), outputlevel="default", level=outputlevel,
- descriptor="stdout")
- conf.UpdateDB()
- conf.ReadConfig()
- """Check which outputlevel is defined and update the configuration object
- accordingly.
- """
- conf.outputlevel = outputlevel
- """youtube-dl is really a dependency but the program will run with limited\
- functionality without it so we need to check that here
- """
- if conf.values["youtube-dl"] == "":
- conf.values["youtube-dl"] = subprocess.Popen(["which", "youtube-dl"],
- stdout=subprocess.PIPE).communicate()[0].strip()
- if os.access(conf.values["youtube-dl"], os.F_OK & os.R_OK & os.X_OK):
- printf(_("Found youtube-dl, writing to configuration file."),
- outputlevel="default", level=conf.outputlevel, descriptor="stdout")
- conf.WriteConfig()
- else:
- printf(_("Could not find youtube-dl, it either does not exist, "
- "is not readable or not executable. Please note that "
- "youtube-dl is not needed for the program to run but is"
- " needed to use the download option which won't work otherwise."
- " If youtube-dl isn't found automatically, you may also enter "
- "the path to it in the configuration file."),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- """Variable to save the text that is later sent as e-mail"""
- mailcontent = []
- """Check which options are given on the command line and
- run the corresponding code
- """
- if options.add is True:
- AddSub = True
- if options.channel is not None and options.searchparameter is None:
- NewSubscription = subscription.sub(type="channel",
- name=options.channel, conf=conf)
- elif options.channel is not None and options.searchparameter is not None:
- NewSubscription = subscription.sub(type="channel",
- name=options.channel, search=options.searchparameter, conf=conf)
- elif options.channel is None and options.searchparameter is not None:
- NewSubscription = subscription.sub(type="search",
- name=_("Search_"), search=options.searchparameter, conf=conf)
- elif options.playlist is not None:
- if options.searchparameter is not None:
- printf(_("Playlists do not support searching, search option will "
- "be ignored!"), outputlevel="default", level=conf.outputlevel,
- descriptor="stderr")
- NewSubscription = subscription.sub(type="playlist",
- name=options.playlist, conf=conf)
- else:
- printf(_("No valid subscription options given, "
- "subscription could not be added"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- AddSub = False
- if AddSub is True:
- NewSubscription.AddSub()
- NewSubscription.UpdateVideos()
- printf(_("New subscription ") + NewSubscription.GetTitle()
- + _(" successfully added"), outputlevel="default", level=conf.outputlevel,
- descriptor="stdout")
- elif options.list is True:
- try:
- database = sqlite3.connect(conf.dbpath)
- cursor = database.cursor()
- except sqlite3.OperationalError:
- printf(_("Could not access the database, please check path "
- "and permissions and try again!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- else:
- cursor.execute("SELECT id, title FROM subscriptions")
- Listofsubscriptions = cursor.fetchall()
- if len(Listofsubscriptions) != 0:
- printf(_("ID Title"), outputlevel="default", level=conf.outputlevel,
- descriptor="stdout")
- for subscription in Listofsubscriptions:
- if subscription[0] is not None:
- printf(str(subscription[0]) + " " + subscription[1],
- outputlevel="default", level=conf.outputlevel, descriptor="stdout")
- else:
- printf(_("No subscriptions added yet, add one!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stdout")
- database.close()
- elif options.deleteid is not None:
- try:
- DeleteId = int(options.deleteid)
- Subscription = subscription.sub(type="", name="delete",
- id=DeleteId, conf=conf)
- except ValueError:
- printf(_("Invalid Option, please use the ID of the subscription"
- "you want to delete as parameter for the remove option"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- exit_status = Subscription.Delete()
- elif options.update is True:
- listofsubscriptions = []
- try:
- database = sqlite3.connect(conf.dbpath)
- cursor = database.cursor()
- except sqlite3.OperationalError:
- printf(_("Could not access the database, please check path and "
- "permissions and try again!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- else:
- cursor.execute("SELECT id,title,type,name,searchstring,directory \
- FROM subscriptions")
- subscriptions = cursor.fetchall()
- database.close()
- for element in subscriptions:
- listofsubscriptions.append(subscription.sub(id=element[0],
- title=element[1], type=element[2], name=element[3],
- search=element[4], directory=element[5], conf=conf))
- for element in listofsubscriptions:
- element.UpdateVideos()
- elif options.download is True:
- listofsubscriptions = []
- try:
- database = sqlite3.connect(conf.dbpath)
- cursor = database.cursor()
- except sqlite3.OperationalError:
- printf(_("Could not access the database, please check path"
- "and permissions and try again!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- else:
- cursor.execute("SELECT id,title,type,name,searchstring,directory \
- FROM subscriptions")
- subscriptions = cursor.fetchall()
- itag_value = conf.GetYoutubeParameter()
- if itag_value == 0:
- printf(_("Codec and resolution could not be determined, using maximum "
- "possible value"), outputlevel="verbose",
- level=conf.outputlevel, descriptor="stderr")
- itag_value = 38
- for element in subscriptions:
- listofsubscriptions.append(subscription.sub(id=element[0],
- title=element[1], type=element[2], name=element[3],
- search=element[4], directory=element[5], conf=conf))
- videosdownloaded = 0
- videosfailed = 0
- for element in listofsubscriptions:
- element.GetVideos()
- element.DownloadVideos(itag_value)
- for entry in element.DownloadedVideos:
- mailcontent.append(entry)
- videosdownloaded = len(mailcontent)
- videosfailed = videosfailed + element.FailedVideos
- if videosdownloaded > 0 and conf.values["notify"] == "yes":
- MailText = ""
- msg = MIMEMultipart()
- if videosdownloaded == 1:
- msg["Subject"] = _("Downloaded %i new video") % videosdownloaded
- MailText = _("The following episode has been downloaded by stov: \n\n")
- else:
- msg["Subject"] = _("Downloaded %i new videos") % videosdownloaded
- MailText = _("The following episodes have been downloaded by stov: \n\n")
- msg["From"] = "stov <%s>" % conf.values["mailfrom"]
- msg["To"] = "<%s>" % conf.values["mailto"]
- for line in mailcontent:
- MailText += line + "\n"
- msgtext = MIMEText(MailText.encode("utf8"), _charset="utf8")
- msg.attach(msgtext)
- serverconnection = smtplib.SMTP()
- try:
- if sys.version_info >= (3, 0):
- serverconnection.connect(conf.values["mailhost"], conf.values["smtpport"])
- else:
- serverconnection.connect(str(conf.values["mailhost"]),
- str(conf.values["smtpport"]))
- except smtplib.SMTPConnectError:
- printf(sys.stderr, _("Could not connect to the smtp server, please"
- " check your settings!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- try:
- serverconnection.starttls()
- except smtplib.SMTPException:
- printf(_("TLS not available, proceeding unencrypted"),
- outputlevel="default", level=conf.outputlevel, descriptor="stdout")
- if conf.values["auth_needed"] == "yes":
- try:
- serverconnection.login(conf.values["user_name"], conf.values["password"])
- except smtplib.SMTPAuthenticationError:
- printf(_("Authentication failed, please check user name"
- "and password!"), outputlevel="default", level=conf.outputlevel,
- descriptor="stderr")
- except smtplib.SMTPException:
- printf(_("Could not authenticate, server probably does not"
- " support authentication!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- try:
- serverconnection.sendmail(conf.values["mailfrom"], conf.values["mailto"],
- msg.as_string())
- except smtplib.SMTPRecipientsRefused:
- printf(_("The server refused the recipient address, "
- "please check your settings"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- except smtplib.SMTPSenderRefused:
- printf(_("The server refused the sender address, "
- "please check your settings"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- serverconnection.quit()
- elif videosdownloaded == 0 and videosfailed == 0:
- if conf.values["notify"] == "no":
- printf(_("No videos to be downloaded."), outputlevel="default",
- level=conf.outputlevel, descriptor="stdout")
- elif conf.values["notify"] == "no":
- if videosfailed == 0:
- printf(_("The following videos have been downloaded:\n"),
- outputlevel="default", level=conf.outputlevel, descriptor="stdout")
- for i in mailcontent:
- printf(i, outputlevel="default", level=conf.outputlevel,
- descriptor="stdout")
- else:
- printf(_("Could not determine how you want to be informed "
- "about new videos, please check the notify parameter "
- "in your configuration"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- elif options.subscriptionid is not None:
- try:
- database = sqlite3.connect(conf.dbpath)
- cursor = database.cursor()
- except sqlite3.OperationalError:
- printf(_("Could not access the database, please check"
- "path and permissions and try again!"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- else:
- subscription_video_select = "SELECT id,title,type,name,searchstring,\
- directory FROM subscriptions where id=?"
- cursor.execute(subscription_video_select, (options.subscriptionid,))
- Data = cursor.fetchall()
- if Data != []:
- Subscription = subscription.sub(id=Data[0][0], title=Data[0][1],
- type=Data[0][2], name=Data[0][3], search=Data[0][4],
- directory=Data[0][5], conf=conf)
- Subscription.GetVideos()
- Subscription.PrintVideos()
- else:
- printf(_("Invalid subscription, please check the list and try again"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- elif options.catchup is not None:
- try:
- database = sqlite3.connect(conf.dbpath)
- cursor = database.cursor()
- except sqlite3.OperationalError:
- printf(_("Could not access the database, please check "
- "path and permissions and try again!"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- else:
- subscriptionselect = "SELECT title from subscriptions where id=?"
- cursor.execute(subscriptionselect, (options.catchup,))
- sub_data = cursor.fetchall()
- if sub_data != []:
- catchup_sql = "UPDATE videos SET downloaded = 1 WHERE subscription_id =?"
- cursor.execute(catchup_sql, (options.catchup,))
- database.commit()
- database.close()
- else:
- printf(_("Subscription could not be updated, "
- "please check if the ID given is correct"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
- elif options.cleanup is True:
- subscriptions_list = []
- try:
- database = sqlite3.connect(conf.dbpath)
- cursor = database.cursor()
- except sqlite3.OperationalError:
- printf(_("Could not access the database, please check path and "
- "permissions and try again!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- else:
- cursor.execute("SELECT id,title,type,name,searchstring,directory \
- FROM subscriptions")
- subscriptions = cursor.fetchall()
- database.close()
- for element in subscriptions:
- subscriptions_list.append(subscription.sub(id=element[0],
- title=element[1], type=element[2], name=element[3],
- search=element[4], directory=element[5], conf=conf))
- for element in subscriptions_list:
- element.CheckAndDelete()
- try:
- database = sqlite3.connect(conf.dbpath)
- cursor = database.cursor()
- except sqlite3.OperationalError:
- printf(_("Could not access the database, please check path and "
- "permissions and try again!"), outputlevel="default",
- level=conf.outputlevel, descriptor="stderr")
- else:
- cursor.execute("VACUUM")
- database.close()
- elif options.license is True:
- printf("""
- stov is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, version 2 of the License.
- stov is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with stov. If not, see <http://www.gnu.org/licenses/>.
- """, outputlevel="default", level=conf.outputlevel, descriptor="stdout")
- elif options.version is True:
- printf("0.4wip", outputlevel="default", level=conf.outputlevel,
- descriptor="stdout")
- else:
- parser.print_help()
- """Remove the lock file and end the program so it can be run again"""
- try:
- os.remove("/tmp/stov.lock")
- if exit_status is True:
- sys.exit(0)
- else:
- sys.exit(1)
- except os.error:
- printf(_("Could not delete the lock file. Please check what "
- "went wrong and clean up manually!"),
- outputlevel="default", level=conf.outputlevel, descriptor="stderr")
|