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, JsonResponse
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 old_path = os.path.join(settings.NEW_PATH, filename)
74 new_path = os.path.join(settings.UNMANAGED_PATH, filename)
75 new_dir = os.path.split(new_path)[0]
76 if not os.path.isdir(new_dir):
79 if not os.path.isfile(old_path):
83 os.link(old_path, new_path)
86 # destination file exists, don't overwrite it
87 # TODO: this should probably be more informative
88 return redirect(file_new, filename)
90 return redirect(list_new)
94 @permission_required('archive.change_audiobook')
95 def remove_to_archive(request, aid):
96 """ move a managed file to the unmanaged files dir """
98 audiobook = get_object_or_404(models.Audiobook, id=aid)
99 old_path = audiobook.source_file.path
100 new_path = os.path.join(settings.UNMANAGED_PATH,
101 str(audiobook.source_file)[len(settings.FILES_SAVE_PATH):].lstrip('/'))
102 new_dir = os.path.split(new_path)[0]
103 if not os.path.isdir(new_dir):
106 if not os.path.isfile(old_path):
110 try_new_path = new_path
114 os.link(old_path, try_new_path)
116 # destination file exists, don't overwrite it
118 parts = new_path.rsplit('.', 1)
119 parts[0] += '_%d' % try_number
120 try_new_path = ".".join(parts)
126 return redirect(list_unmanaged)
129 @permission_required('archive.change_audiobook')
130 def move_to_new(request, filename):
131 """ move a unmanaged file to new files dir """
133 old_path = os.path.join(settings.UNMANAGED_PATH, filename)
134 new_path = os.path.join(settings.NEW_PATH, filename)
135 new_dir = os.path.split(new_path)[0]
136 if not os.path.isdir(new_dir):
139 if not os.path.isfile(old_path):
143 os.link(old_path, new_path)
146 # destination file exists, don't overwrite it
147 # TODO: this should probably be more informative
148 return redirect(reverse(file_unmanaged, args=[filename]) + "?exists=1")
150 return redirect(list_unmanaged)
154 @permission_required('archive.change_audiobook')
155 def publish(request, aid, publish=True):
156 """ mark file for publishing """
157 audiobook = get_object_or_404(models.Audiobook, id=aid)
158 audiobook.publish(request.user, publish=publish)
159 return redirect(file_managed, aid)
163 @permission_required('archive.change_audiobook')
164 def cancel_publishing(request, aid):
165 """ cancel scheduled publishing """
166 audiobook = get_object_or_404(models.Audiobook, id=aid)
168 audiobook.mp3_status = None
169 audiobook.ogg_status = None
170 audiobook.youtube_status = None
171 audiobook.youtube_queued = None
173 return redirect(file_managed, aid)
176 def download(request, aid, which="source"):
177 if which not in ("source", "mp3", "ogg", 'mkv'):
179 audiobook = get_object_or_404(models.Audiobook, id=aid)
183 file_ = getattr(audiobook, "%s_file" % field)
186 ext = file_.path.rsplit('.', 1)[-1]
187 response = HttpResponse(content_type='application/force-download')
189 response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % (
190 quote(audiobook.title.encode('utf-8'), safe=''), ext)
191 with open(file_.path, 'rb') as f:
192 response.write(f.read())
193 #response['X-Sendfile'] = file_.path.encode('utf-8')
197 def list_publishing(request):
198 objects = models.Audiobook.objects.exclude(
199 mp3_status=None, ogg_status=None, youtube_status=None
200 ).order_by("youtube_queued", "title")
201 objects_by_status = {}
205 statuses.add((o.mp3_status, o.get_mp3_status_display()))
207 statuses.add((o.ogg_status, o.get_ogg_status_display()))
209 statuses.add((o.youtube_status, o.get_youtube_status_display()))
210 for status in statuses:
211 objects_by_status.setdefault(status, []).append(o)
212 status_objects = sorted(objects_by_status.items(), reverse=True)
214 return render(request, "archive/list_publishing.html", locals())
217 class AudiobookList(ListView):
218 def get_queryset(self):
219 qs = models.Audiobook.objects.all()
220 if 's' in self.request.GET:
221 qs = qs.annotate(s=SearchVector('title', 'slug')).filter(s=self.request.GET['s'])
225 @permission_required('archive.change_audiobook')
226 def file_managed(request, id):
227 audiobook = get_object_or_404(models.Audiobook, id=id)
230 form = AudiobookForm(request.POST, instance=audiobook)
238 if audiobook.source_file:
239 path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
242 tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
245 form = AudiobookForm(instance=audiobook)
248 request.user.is_authenticated and
249 request.user.oauthconnection_set.filter(access=True).exists())
252 parts_count = audiobook.parts_count
254 series = models.Audiobook.objects.filter(slug=audiobook.slug)
255 if not audiobook.index:
256 alerts.append(_('There is more than one part, but index is not set.'))
257 if set(series.values_list('index', flat=True)) != set(range(1, parts_count + 1)):
258 alerts.append(_('Part indexes are not 1..%(parts_count)d.') % {"parts_count": parts_count})
260 from youtube.models import YouTube
261 youtube = YouTube.objects.first()
262 youtube_title = youtube.get_title(audiobook)
263 youtube_description = youtube.get_description(audiobook)
266 return render(request, "archive/file_managed.html", locals())
269 def list_unmanaged(request):
270 objects = sorted(all_files(settings.UNMANAGED_PATH))
271 return render(request, "archive/list_unmanaged.html", locals())
274 def file_unmanaged(request, filename):
275 tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename))
279 err_exists = request.GET.get('exists')
280 return render(request, "archive/file_unmanaged.html", locals())
283 class BookView(ListView):
284 template_name = 'archive/book.html'
286 def get_queryset(self):
287 qs = models.Audiobook.objects.filter(slug=self.kwargs["slug"]).order_by(
293 if last_vol is None or last_vol.youtube_volume_index != b.youtube_volume_index:
296 if last_vol_sub is None or b.youtube_volume:
297 last_vol_sub = last_vol
298 last_vol_sub.total_for_sub = 0
299 last_vol.total += b.duration
300 last_vol_sub.total_for_sub += b.duration
301 b.subtotal = last_vol_sub.total_for_sub
304 def book_json(request, slug):
305 qs = models.Audiobook.objects.filter(slug=slug).order_by(
308 return JsonResponse({
312 "part": item.part_name,
313 "mp3_status": item.get_mp3_status_display(),
314 "ogg_status": item.get_ogg_status_display(),
315 "youtube_status": item.get_youtube_status_display(),
322 @permission_required('archive.change_audiobook')
323 def book_youtube_volume(request, aid):
324 audiobook = get_object_or_404(models.Audiobook, id=aid)
325 slug = audiobook.slug
326 cur_vol = audiobook.youtube_volume
327 new_vol = request.POST.get('volume', '')
329 audiobook.youtube_volume = new_vol
332 for a in models.Audiobook.objects.filter(slug=slug, youtube_volume=cur_vol, index__gt=audiobook.index).order_by('index'):
333 if a.youtube_volume != cur_vol:
335 a.youtube_volume = new_vol
338 return redirect('book', audiobook.slug)