1 from datetime import datetime
4 from urllib.parse import quote
6 from archive import settings
7 from django.contrib.auth.decorators import permission_required
8 from django.contrib.postgres.search import SearchVector
9 from django.urls import reverse
10 from django.db.models import Q, Max
11 from django.http import Http404, HttpResponse
12 from django.shortcuts import render, redirect, get_object_or_404
13 from django.utils.translation import gettext as _
14 from django.views.decorators.http import require_POST
15 from django.views.generic import ListView
19 from archive.constants import status
20 from archive import models
21 from archive.forms import AudiobookForm
22 from archive import tasks
23 from archive.utils import all_files
26 def list_new(request):
27 path = settings.NEW_PATH
28 objects = sorted(all_files(path))
29 return render(request, "archive/list_new.html", locals())
32 @permission_required('archive.change_audiobook')
33 def file_new(request, filename):
35 root_filepath = os.path.join(settings.NEW_PATH, filename)
37 form = AudiobookForm(request.POST)
40 form.save(path=filepath)
43 return redirect(list_new)
46 tags = mutagen.File(root_filepath)
53 if isinstance(value, list):
59 d[tag] = models.Project.objects.get(name=d[tag]).pk
60 except models.Project.DoesNotExist:
64 form = AudiobookForm(initial=d)
65 return render(request, "archive/file_new.html", locals())
69 @permission_required('archive.change_audiobook')
70 def move_to_archive(request, filename):
71 """ move a new file to the unmanaged files dir """
73 filename_str = filename.encode('utf-8')
74 old_path = os.path.join(settings.NEW_PATH, filename_str)
75 new_path = os.path.join(settings.UNMANAGED_PATH, filename_str)
76 new_dir = os.path.split(new_path)[0]
77 if not os.path.isdir(new_dir):
80 if not os.path.isfile(old_path):
84 os.link(old_path, new_path)
87 # destination file exists, don't overwrite it
88 # TODO: this should probably be more informative
89 return redirect(file_new, filename)
91 return redirect(list_new)
95 @permission_required('archive.change_audiobook')
96 def remove_to_archive(request, aid):
97 """ move a managed file to the unmanaged files dir """
99 audiobook = get_object_or_404(models.Audiobook, id=aid)
100 old_path = audiobook.source_file.path
101 new_path = os.path.join(settings.UNMANAGED_PATH,
102 str(audiobook.source_file)[len(settings.FILES_SAVE_PATH):].lstrip('/'))
103 new_dir = os.path.split(new_path)[0]
104 if not os.path.isdir(new_dir):
107 if not os.path.isfile(old_path):
111 try_new_path = new_path
115 os.link(old_path, try_new_path)
117 # destination file exists, don't overwrite it
119 parts = new_path.rsplit('.', 1)
120 parts[0] += '_%d' % try_number
121 try_new_path = ".".join(parts)
127 return redirect(list_unmanaged)
130 @permission_required('archive.change_audiobook')
131 def move_to_new(request, filename):
132 """ move a unmanaged file to new files dir """
134 filename_str = filename.encode('utf-8')
135 old_path = os.path.join(settings.UNMANAGED_PATH, filename_str)
136 new_path = os.path.join(settings.NEW_PATH, filename_str)
137 new_dir = os.path.split(new_path)[0]
138 if not os.path.isdir(new_dir):
141 if not os.path.isfile(old_path):
145 os.link(old_path, new_path)
148 # destination file exists, don't overwrite it
149 # TODO: this should probably be more informative
150 return redirect(reverse(file_unmanaged, args=[filename]) + "?exists=1")
152 return redirect(list_unmanaged)
156 @permission_required('archive.change_audiobook')
157 def publish(request, aid, publish=True):
158 """ mark file for publishing """
159 audiobook = get_object_or_404(models.Audiobook, id=aid)
160 audiobook.prepare_for_publish()
162 audiobook.publish(request.user)
163 return redirect(file_managed, aid)
167 @permission_required('archive.change_audiobook')
168 def cancel_publishing(request, aid):
169 """ cancel scheduled publishing """
170 audiobook = get_object_or_404(models.Audiobook, id=aid)
172 audiobook.mp3_status = None
173 audiobook.ogg_status = None
174 audiobook.youtube_status = None
175 audiobook.youtube_queued = None
177 return redirect(file_managed, aid)
180 def download(request, aid, which="source"):
181 if which not in ("source", "mp3", "ogg", 'mkv'):
183 audiobook = get_object_or_404(models.Audiobook, id=aid)
187 file_ = getattr(audiobook, "%s_file" % field)
190 ext = file_.path.rsplit('.', 1)[-1]
191 response = HttpResponse(content_type='application/force-download')
193 response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % (
194 quote(audiobook.title.encode('utf-8'), safe=''), ext)
195 with open(file_.path, 'rb') as f:
196 response.write(f.read())
197 #response['X-Sendfile'] = file_.path.encode('utf-8')
201 def list_publishing(request):
202 objects = models.Audiobook.objects.exclude(
203 mp3_status=None, ogg_status=None, youtube_status=None
204 ).order_by("youtube_queued", "title")
205 objects_by_status = {}
209 statuses.add((o.mp3_status, o.get_mp3_status_display()))
211 statuses.add((o.ogg_status, o.get_ogg_status_display()))
213 statuses.add((o.youtube_status, o.get_youtube_status_display()))
214 for status in statuses:
215 objects_by_status.setdefault(status, []).append(o)
216 status_objects = sorted(objects_by_status.items(), reverse=True)
218 return render(request, "archive/list_publishing.html", locals())
221 class AudiobookList(ListView):
222 def get_queryset(self):
223 qs = models.Audiobook.objects.all()
224 if 's' in self.request.GET:
225 qs = qs.annotate(s=SearchVector('title', 'slug')).filter(s=self.request.GET['s'])
229 @permission_required('archive.change_audiobook')
230 def file_managed(request, id):
231 audiobook = get_object_or_404(models.Audiobook, id=id)
234 form = AudiobookForm(request.POST, instance=audiobook)
242 if audiobook.source_file:
243 path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
246 tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
249 form = AudiobookForm(instance=audiobook)
252 request.user.is_authenticated and
253 request.user.oauthconnection_set.filter(access=True).exists())
256 parts_count = audiobook.parts_count
258 series = models.Audiobook.objects.filter(slug=audiobook.slug)
259 if not audiobook.index:
260 alerts.append(_('There is more than one part, but index is not set.'))
261 if set(series.values_list('index', flat=True)) != set(range(1, parts_count + 1)):
262 alerts.append(_('Part indexes are not 1..%(parts_count)d.') % {"parts_count": parts_count})
264 from youtube.models import YouTube
265 youtube = YouTube.objects.first()
266 youtube_title = youtube.get_title(audiobook)
267 youtube_description = youtube.get_description(audiobook)
270 return render(request, "archive/file_managed.html", locals())
273 def list_unmanaged(request):
274 objects = sorted(all_files(settings.UNMANAGED_PATH))
275 return render(request, "archive/list_unmanaged.html", locals())
278 def file_unmanaged(request, filename):
279 tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8')))
283 err_exists = request.GET.get('exists')
284 return render(request, "archive/file_unmanaged.html", locals())
287 class BookView(ListView):
288 template_name = 'archive/book.html'
290 def get_queryset(self):
291 qs = models.Audiobook.objects.filter(slug=self.kwargs["slug"]).order_by(
297 if last_vol is None or last_vol.youtube_volume_index != b.youtube_volume_index:
300 if last_vol_sub is None or b.youtube_volume:
301 last_vol_sub = last_vol
302 last_vol_sub.total_for_sub = 0
303 last_vol.total += b.duration
304 last_vol_sub.total_for_sub += b.duration
305 b.subtotal = last_vol_sub.total_for_sub
309 @permission_required('archive.change_audiobook')
310 def book_youtube_volume(request, aid):
311 audiobook = get_object_or_404(models.Audiobook, id=aid)
312 slug = audiobook.slug
313 cur_vol = audiobook.youtube_volume
314 new_vol = request.POST.get('volume', '')
316 audiobook.youtube_volume = new_vol
319 for a in models.Audiobook.objects.filter(slug=slug, youtube_volume=cur_vol, index__gt=audiobook.index).order_by('index'):
320 if a.youtube_volume != cur_vol:
322 a.youtube_volume = new_vol
325 return redirect('book', audiobook.slug)